diff --git a/ERCS/erc-7007.md b/ERCS/erc-7007.md index 1f439f0085..34801db404 100644 --- a/ERCS/erc-7007.md +++ b/ERCS/erc-7007.md @@ -1,10 +1,10 @@ --- 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 +title: Verifiable AI-Generated Content Token +description: An ERC-721 extension for verifiable AI-generated content tokens using Zero-Knowledge and Optimistic Machine Learning techniques +author: Cathie So (@socathie), Xiaohang Yu (@xhyumiracle), Conway (@0x1cc), Lee Ting Ting (@tina1998612), Kartin discussions-to: https://ethereum-magicians.org/t/eip-7007-zkml-aigc-nfts-an-erc-721-extension-interface-for-zkml-based-aigc-nfts/14216 -status: Draft +status: Review type: Standards Track category: ERC created: 2023-05-10 @@ -13,23 +13,39 @@ 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`. +The verifiable AI-generated content (AIGC) non-fungible token (NFT) standard is an extension of the [ERC-721](./eip-721.md) token standard for AIGC. It proposes a set of interfaces for basic interactions and enumerable interactions for AIGC-NFTs. The standard includes a `mint` and `verify` function interface, a new `Mint` event, optional `Enumerable` and `Updatable` extensions, and a JSON schema for AIGC-NFT metadata. Additionally, it incorporates Zero-Knowledge Machine Learning (zkML) and Optimistic Machine Learning (opML) capabilities to enable verification of AIGC data correctness. In this standard, the `tokenId` is indexed by the `prompt`. ## 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. +The verifiable AIGC-NFT standard aims to extend the existing [ERC-721](./eip-721.md) token standard to accommodate the unique requirements of AI-generated content NFTs representing models in a collection. This standard provides interfaces to use zkML or opML to verify whether or not the AIGC data for an NFT is generated from a certain ML model with a certain input (prompt). The proposed interfaces allow for additional functionality related to minting, verifying, and enumerating AIGC-NFTs. Additionally, the metadata schema provides a structured format for storing information related to AIGC-NFTs, such as the prompt used to generate the content and the proof of ownership. -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). +This standard supports two primary types of proofs: validity proofs and fraud proofs. In practice, zkML and opML are commonly employed as the prevailing instances for these types of proofs. Developers can choose their preferred ones. + +In the zkML scenario, this standard enables model owners to publish their trained model and its ZKP verifier to Ethereum. Any user can claim an input (prompt) and publish the inference task. Any node that maintains the model and the proving circuit can perform the inference and proving, and submit the output of inference and the ZK proof for the inference trace to the verifier. The user that initiates the inference task will own the output for the inference of that model and input (prompt). + +In the opML scenario, this standard enables model owners to publish their trained model to Ethereum. Any user can claim an input (prompt) and publish the inference task. Any node that maintains the model can perform the inference and submit the inference output. Other nodes can challenge this result within a predefined challenge period. At the end of the challenge period, the user can verify that they own the output for the inference of that model and prompt. + +This capability is especially beneficial for AI model authors and AI content creators seeking to capitalize on their creations. With this standard, every input prompt and its resulting content can be securely verified on the blockchain. This opens up opportunities for implementing revenue-sharing mechanisms for all AI-generated content (AIGC) NFT sales. AI model authors can now share their models without concerns that open-sourcing will diminish their financial value. + +An example workflow of a zkML AIGC NFT project compliant with this proposal is as follows: + +![zkML Suggested Workflow](../assets/eip-7007/workflow.png) + +There are 4 components in this workflow: +* ML model - contains weights of a pre-trained model; given an inference input, generates the output +* zkML prover - given an inference task with input and output, generates a ZK proof +* AIGC-NFT smart contract - contract compliant with this proposal, with full [ERC-721](./eip-721.md) functionalities +* Verifier smart contract - implements a `verify` function, given an inference task and its ZK proof, returns the verification result as a boolean ## 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.** +**Every compliant contract must implement the `IERC7007`, [`ERC721`](./eip-721.md), and [`ERC165`](./eip-165.md) interfaces.** -The zkML AIGC-NFTs standard includes the following interfaces: +The verifiable AIGC-NFT 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. +`IERC7007`: Defines a `mint` function and a `Mint` event for minting AIGC-NFTs. Defines a `verify` function to check the validity of the combination of prompt and aigcData using zkML/opML techniques. ```solidity pragma solidity ^0.8.18; @@ -43,15 +59,16 @@ interface IERC7007 is IERC165, IERC721 { * @dev Emitted when `tokenId` token is minted. */ event Mint( + address indexed to, uint256 indexed tokenId, bytes indexed prompt, - bytes indexed aigcData, + bytes aigcData, string uri, bytes proof ); /** - * @dev Mint token at `tokenId` given `prompt`, `aigcData`, `uri` and `proof`. + * @dev Mint token at `tokenId` given `to`, `prompt`, `aigcData`, `uri`, and `proof`. `proof` means that we input the ZK proof when using zkML and byte zero when using opML as the verification method. * * Requirements: * - `tokenId` must not exist.' @@ -61,6 +78,7 @@ interface IERC7007 is IERC165, IERC721 { * - `proof` should not include `aigcData` to save gas. */ function mint( + address to, bytes calldata prompt, bytes calldata aigcData, string calldata uri, @@ -68,7 +86,7 @@ interface IERC7007 is IERC165, IERC721 { ) external returns (uint256 tokenId); /** - * @dev Verify the `prompt`, `aigcData` and `proof`. + * @dev Verify the `prompt`, `aigcData`, and `proof`. */ function verify( bytes calldata prompt, @@ -78,7 +96,7 @@ interface IERC7007 is IERC165, IERC721 { } ``` -Optional Extension: Enumerable +### 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. @@ -102,7 +120,39 @@ interface IERC7007Enumerable is IERC7007 { } ``` -ERC-7007 Metadata JSON Schema for reference +### Optional Extension: Updatable + +The **updatable extension** is OPTIONAL for [ERC-7007](./eip-7007.md) smart contracts. This allows your contract to update a token's `aigcData` in the case of opML, where `aigcData` content might change over the challenge period. + +```solidity +pragma solidity ^0.8.18; + +/** + * @title ERC7007 Token Standard, optional updatable extension + */ +interface IERC7007Updatable is IERC7007 { + /** + * @dev Update the `aigcData` of `prompt`. + */ + function update( + bytes calldata prompt, + bytes calldata aigcData, + string calldata uri + ) external; + + /** + * @dev Emitted when `tokenId` token is updated. + */ + event Update( + uint256 indexed tokenId, + bytes indexed prompt, + bytes indexed aigcData, + string uri + ); +} +``` + +### ERC-7007 Metadata JSON Schema for reference ```json { @@ -121,7 +171,6 @@ ERC-7007 Metadata JSON Schema for reference "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" @@ -133,14 +182,40 @@ ERC-7007 Metadata JSON Schema for reference "aigc_data": { "type": "string", "description": "A URI pointing to a resource with mime type image/* representing the asset to which this AIGC NFT represents." + }, + "proof_type": { + "type": "string", + "description": "validity (zkML) or fraud (opML)" } } } ``` +### ML Model Publication + +While this standard does not describe the Machine Learning model publication stage, it is natural and recommended to publish the commitment of the Model to Ethereum separately, before any actual `mint` actions. The model commitment schema choice lies on the AIGC-NFT project issuer party. The commitment should be checked inside the implementation of the `verify` function. + ## Rationale -TBD +### Unique Token Identification + +This specification sets the `tokenId` to be the hash of its corresponding `prompt`, creating a deterministic and collision-resistant way to associate tokens with their unique content generation parameters. This design decision ensures that the same prompt (which corresponds to the same AI-generated content under the same model seed) cannot be minted more than once, thereby preventing duplication and preserving the uniqueness of each NFT within the ecosystem. + +### Generalization to Different Proof Types + +This specification accommodates two proof types: validity proofs for zkML and fraud proofs for opML. Function arguments in `mint` and `verify` are designed for generality, allowing for compatibility with both proof systems. Moreover, the specification includes an updatable extension that specifically serves the requirements of opML. + +### `verify` interface + +We specify a `verify` interface to enforce the correctness of `aigcData`. It is defined as a view function to reduce gas cost. `verify` should return true if and only if `aigcData` is finalized in both zkML and opML. In zkML, it must verify the ZK proof, i.e. `proof`; in opML, it must make sure that the challenging period is finalized, and that the `aigcData` is up-to-date, i.e. has been updated after finalization. Additionally, `proof` can be *empty* in opML. + +### `mint` interface + +We specify a `mint` interface to bind the prompt and `aigcData` with `tokenId`. Notably, it acts differently in zkML and opML cases. In zkML, `mint` should make sure `verify` returns `true`. While in opML, it can be called before finalization. The consideration here is that, limited by the proving difficulty, zkML usually targets simple model inference tasks in practice, making it possible to provide a proof within an acceptable time frame. On the other hand, opML enables large model inference tasks, with a cost of longer confirmation time to achieve the approximate same security level. Mint until opML finalization may not be the best practice considering the existing optimistic protocols. + +### Naming Choice on `update` + +We adopt "update" over "finalize" because a successful challenge happens rarely in practice. Using `update` could avoid calling it for every `tokenId` and save gas. ## Backwards Compatibility @@ -152,12 +227,18 @@ The reference implementation includes sample implementations of the [ERC-7007](. ## Reference Implementation -* [ERC-7007](../assets/eip-7007/contracts/ERC7007.sol) +* ERC-7007 for [zkML](../assets/eip-7007/contracts/ERC7007Zkml.sol) and [opML](../assets/eip-7007/contracts/ERC7007Opml.sol) * [ERC-7007 Enumerable Extension](../assets/eip-7007/contracts/ERC7007Enumerable.sol) ## Security Considerations -Needs discussion. +### Frontrunning Risk + +To address the risk of frontrunning, where an actor could potentially observe and preemptively claim a prompt during the minting process, implementers of this proposal must incorporate a secure prompt-claiming mechanism. Implementations could include time-locks, commit-reveal schemes, or other anti-frontrunning techniques to ensure equitable and secured claim processes for AIGC-NFTs. + +### AIGC Data Change During Challenge Period + +In the opML scenario, it is important to consider that the `aigcData` might change during the challenge period due to disputes or updates. The updatable extension defined here provides a way to handle these updates. Implementations must ensure that updates to `aigcData` are treated as critical state changes that require adherence to the same security and validation protocols as the initial minting process. Indexers should always check for any `Update` event emission. ## Copyright diff --git a/assets/erc-7007/contracts/ERC7007Enumerable.sol b/assets/erc-7007/contracts/ERC7007Enumerable.sol index d8baed3210..fe096209df 100644 --- a/assets/erc-7007/contracts/ERC7007Enumerable.sol +++ b/assets/erc-7007/contracts/ERC7007Enumerable.sol @@ -1,13 +1,13 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "./ERC7007.sol"; +import "./ERC7007Zkml.sol"; import "./IERC7007Enumerable.sol"; /** * @dev Implementation of the {IERC7007Enumerable} interface. */ -abstract contract ERC7007Enumerable is ERC7007, IERC7007Enumerable { +abstract contract ERC7007Enumerable is ERC7007Zkml, IERC7007Enumerable { /** * @dev See {IERC7007Enumerable-tokenId}. */ @@ -24,7 +24,7 @@ abstract contract ERC7007Enumerable is ERC7007, IERC7007Enumerable { */ function supportsInterface( bytes4 interfaceId - ) public view virtual override(IERC165, ERC7007) returns (bool) { + ) public view virtual override(IERC165, ERC7007Zkml) returns (bool) { return interfaceId == type(IERC7007Enumerable).interfaceId || super.supportsInterface(interfaceId); @@ -34,12 +34,13 @@ abstract contract ERC7007Enumerable is ERC7007, IERC7007Enumerable { * @dev See {IERC7007-mint}. */ function mint( + address to, 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); + ) public virtual override(ERC7007Zkml, IERC7007) returns (uint256 tokenId_) { + tokenId_ = ERC7007Zkml.mint(to, prompt_, aigcData, uri, proof); prompt[tokenId_] = string(prompt_); tokenId[prompt_] = tokenId_; } @@ -50,5 +51,5 @@ contract MockERC7007Enumerable is ERC7007Enumerable { string memory name_, string memory symbol_, address verifier_ - ) ERC7007(name_, symbol_, verifier_) {} + ) ERC7007Zkml(name_, symbol_, verifier_) {} } \ No newline at end of file diff --git a/assets/erc-7007/contracts/ERC7007Opml.sol b/assets/erc-7007/contracts/ERC7007Opml.sol new file mode 100644 index 0000000000..40b8f12ebe --- /dev/null +++ b/assets/erc-7007/contracts/ERC7007Opml.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +import "./IERC7007Updatable.sol"; +import "./IOpmlLib.sol"; + +/** + * @dev Implementation of the {IERC7007} interface. + */ +contract ERC7007Opml is ERC165, IERC7007Updatable, ERC721URIStorage { + address public immutable opmlLib; + mapping (uint256 => uint256) public tokenIdToRequestId; + + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor( + string memory name_, + string memory symbol_, + address opmlLib_ + ) ERC721(name_, symbol_) { + opmlLib = opmlLib_; + } + + /** + * @dev See {IERC7007-mint}. + */ + function mint( + address to, + bytes calldata prompt, + bytes calldata aigcData, + string calldata uri, + bytes calldata proof + ) public virtual override returns (uint256 tokenId) { + tokenId = uint256(keccak256(prompt)); + _safeMint(to, tokenId); + string memory tokenUri = string( + abi.encodePacked( + "{", + uri, + ', "prompt": "', + string(prompt), + '", "aigc_data": "', + string(aigcData), + '"}' + ) + ); + _setTokenURI(tokenId, tokenUri); + + tokenIdToRequestId[tokenId] = IOpmlLib(opmlLib).initOpmlRequest(prompt); + IOpmlLib(opmlLib).uploadResult(tokenIdToRequestId[tokenId], aigcData); + + emit Mint(to, 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) { + uint256 tokenId = uint256(keccak256(prompt)); + bytes memory output = IOpmlLib(opmlLib).getOutput(tokenIdToRequestId[tokenId]); + + return IOpmlLib(opmlLib).isFinalized(tokenIdToRequestId[tokenId]) && (keccak256(output) == keccak256(aigcData)); + } + + /** + * @dev See {IERC7007Updatable-update}. + */ + function update( + bytes calldata prompt, + bytes calldata aigcData, + string calldata uri + ) public virtual override { + require(verify(prompt, aigcData, prompt), "ERC7007: invalid aigcData"); // proof argument is not used in verify() function for opML, so we can pass prompt as proof + uint256 tokenId = uint256(keccak256(prompt)); + string memory tokenUri = string( + abi.encodePacked( + "{", + uri, + ', "prompt": "', + string(prompt), + '", "aigc_data": "', + string(aigcData), + '"}' + ) + ); + require(keccak256(bytes(tokenUri)) != keccak256(bytes(tokenURI(tokenId))), "ERC7007: token uri is not changed"); + + emit Update(tokenId, prompt, aigcData, uri); + } + + /** + * @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/erc-7007/contracts/ERC7007.sol b/assets/erc-7007/contracts/ERC7007Zkml.sol similarity index 90% rename from assets/erc-7007/contracts/ERC7007.sol rename to assets/erc-7007/contracts/ERC7007Zkml.sol index 5610225acc..2478115133 100644 --- a/assets/erc-7007/contracts/ERC7007.sol +++ b/assets/erc-7007/contracts/ERC7007Zkml.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; @@ -9,7 +9,7 @@ import "./IVerifier.sol"; /** * @dev Implementation of the {IERC7007} interface. */ -contract ERC7007 is ERC165, IERC7007, ERC721URIStorage { +contract ERC7007Zkml is ERC165, IERC7007, ERC721URIStorage { address public immutable verifier; /** @@ -27,6 +27,7 @@ contract ERC7007 is ERC165, IERC7007, ERC721URIStorage { * @dev See {IERC7007-mint}. */ function mint( + address to, bytes calldata prompt, bytes calldata aigcData, string calldata uri, @@ -34,7 +35,7 @@ contract ERC7007 is ERC165, IERC7007, ERC721URIStorage { ) public virtual override returns (uint256 tokenId) { require(verify(prompt, aigcData, proof), "ERC7007: invalid proof"); tokenId = uint256(keccak256(prompt)); - _safeMint(msg.sender, tokenId); + _safeMint(to, tokenId); string memory tokenUri = string( abi.encodePacked( "{", @@ -47,7 +48,7 @@ contract ERC7007 is ERC165, IERC7007, ERC721URIStorage { ) ); _setTokenURI(tokenId, tokenUri); - emit Mint(tokenId, prompt, aigcData, uri, proof); + emit Mint(to, tokenId, prompt, aigcData, uri, proof); } /** diff --git a/assets/erc-7007/contracts/IERC7007.sol b/assets/erc-7007/contracts/IERC7007.sol index 359b6b626d..6a86f49319 100644 --- a/assets/erc-7007/contracts/IERC7007.sol +++ b/assets/erc-7007/contracts/IERC7007.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; @@ -6,22 +6,22 @@ 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( + address indexed to, uint256 indexed tokenId, bytes indexed prompt, - bytes indexed aigcData, + bytes aigcData, string uri, bytes proof ); /** - * @dev Mint token at `tokenId` given `prompt`, `aigcData`, `uri` and `proof`. + * @dev Mint token at `tokenId` given `to`, `prompt`, `aigcData`, `uri` and `proof`. `proof` means that we input the ZK proof when using zkML and byte zero when using opML as the verification method. * * Requirements: * - `tokenId` must not exist.' @@ -31,6 +31,7 @@ interface IERC7007 is IERC165, IERC721 { * - `proof` should not include `aigcData` to save gas. */ function mint( + address to, bytes calldata prompt, bytes calldata aigcData, string calldata uri, diff --git a/assets/erc-7007/contracts/IERC7007Enumerable.sol b/assets/erc-7007/contracts/IERC7007Enumerable.sol index 6055402b80..9d7ec0db0a 100644 --- a/assets/erc-7007/contracts/IERC7007Enumerable.sol +++ b/assets/erc-7007/contracts/IERC7007Enumerable.sol @@ -1,11 +1,10 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT 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 { /** diff --git a/assets/erc-7007/contracts/IERC7007Updatable.sol b/assets/erc-7007/contracts/IERC7007Updatable.sol new file mode 100644 index 0000000000..1e1b0b11f8 --- /dev/null +++ b/assets/erc-7007/contracts/IERC7007Updatable.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "./IERC7007.sol"; + +/** + * @title ERC7007 Token Standard, optional updatable extension + */ +interface IERC7007Updatable is IERC7007 { + /** + * @dev Update the `aigcData` of `prompt`. + */ + function update( + bytes calldata prompt, + bytes calldata aigcData, + string calldata uri + ) external; + + /** + * @dev Emitted when `tokenId` token is updated. + */ + event Update( + uint256 indexed tokenId, + bytes indexed prompt, + bytes indexed aigcData, + string uri + ); +} diff --git a/assets/erc-7007/contracts/IOpmlLib.sol b/assets/erc-7007/contracts/IOpmlLib.sol new file mode 100644 index 0000000000..3eae5b5ae3 --- /dev/null +++ b/assets/erc-7007/contracts/IOpmlLib.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +interface IOpmlLib { + function initOpmlRequest(bytes calldata input) external returns (uint256 requestId); // we can construct the initialState using the input, can replace input with prompt (string) + function uploadResult(uint256 requestId, bytes calldata output) external; + function startChallenge(uint256 requestId, bytes32 finalState) external returns (uint256 challengeId); + function respondState(uint256 challengeId, bytes32 stateHash) external; + function proposeState(uint256 challengeId, bytes32 stateHash) external; + function assertStateTransition(uint256 challengeId) external; + function isFinalized(uint256 requestId) external view returns (bool); + function getOutput(uint256 requestId) external view returns (bytes memory output); +} \ No newline at end of file diff --git a/assets/erc-7007/contracts/IVerifier.sol b/assets/erc-7007/contracts/IVerifier.sol index 3d918c70ba..115891338a 100644 --- a/assets/erc-7007/contracts/IVerifier.sol +++ b/assets/erc-7007/contracts/IVerifier.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; interface IVerifier { diff --git a/assets/erc-7007/contracts/MockOpmlLib.sol b/assets/erc-7007/contracts/MockOpmlLib.sol new file mode 100644 index 0000000000..e5efcd6fdb --- /dev/null +++ b/assets/erc-7007/contracts/MockOpmlLib.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "./IOpmlLib.sol"; +contract MockOpmlLib is IOpmlLib { + uint256 public currentRequestId; + mapping (uint256 => bytes) public requestIdToOutput; + + function initOpmlRequest(bytes calldata input) + external + override + returns (uint256 requestId) + { + return currentRequestId++; + } + + function uploadResult(uint256 requestId, bytes calldata output) + external + override + { + requestIdToOutput[requestId] = output; + } + + function startChallenge(uint256 requestId, bytes32 finalState) + external + override + returns (uint256 challengeId) + { + return 0; + } + + function respondState(uint256 challengeId, bytes32 stateHash) + external + override + {} + + function proposeState(uint256 challengeId, bytes32 stateHash) + external + override + {} + + function assertStateTransition(uint256 challengeId) external override {} + + function isFinalized(uint256 requestId) + external + view + override + returns (bool) + { + return true; + } + + function getOutput(uint256 requestId) + external + view + override + returns (bytes memory output) + { + return requestIdToOutput[requestId]; + } +} \ No newline at end of file diff --git a/assets/erc-7007/contracts/MockVerifier.sol b/assets/erc-7007/contracts/MockVerifier.sol index 4c1a771272..86cb50f43e 100644 --- a/assets/erc-7007/contracts/MockVerifier.sol +++ b/assets/erc-7007/contracts/MockVerifier.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import "./IVerifier.sol"; diff --git a/assets/erc-7007/test/test.js b/assets/erc-7007/test/test.js index 31777ecb3e..4728191951 100644 --- a/assets/erc-7007/test/test.js +++ b/assets/erc-7007/test/test.js @@ -1,8 +1,3 @@ -const { - time, - loadFixture, -} = require("@nomicfoundation/hardhat-network-helpers"); -const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); const { expect } = require("chai"); const { BigNumber } = require("ethers"); @@ -14,17 +9,17 @@ async function deployVerifierFixture() { } const prompt = ethers.utils.toUtf8Bytes("test"); const aigcData = ethers.utils.toUtf8Bytes("test"); -const uri = '"name": "test", "description": "test", "image": "test", "aigc_type": "test"'; +const uri = '"name": "test", "description": "test", "image": "test", "aigc_type": "test", "proof_type": "test"'; const validProof = ethers.utils.toUtf8Bytes("valid"); const invalidProof = ethers.utils.toUtf8Bytes("invalid"); const tokenId = BigNumber.from("70622639689279718371527342103894932928233838121221666359043189029713682937432"); -describe("ERC7007.sol", function () { +describe("ERC7007Zkml.sol", function () { async function deployERC7007Fixture() { const verifier = await deployVerifierFixture(); - const ERC7007 = await ethers.getContractFactory("ERC7007"); + const ERC7007 = await ethers.getContractFactory("ERC7007Zkml"); const erc7007 = await ERC7007.deploy("testing", "TEST", verifier.address); await erc7007.deployed(); return erc7007; @@ -34,24 +29,27 @@ describe("ERC7007.sol", function () { it("should mint a token", async function () { const erc7007 = await deployERC7007Fixture(); const [owner] = await ethers.getSigners(); - await erc7007.mint(prompt, aigcData, uri, validProof); + await erc7007.mint(owner.address, 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"); + const [owner] = await ethers.getSigners(); + await expect(erc7007.mint(owner.address, 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"); + const [owner] = await ethers.getSigners(); + await erc7007.mint(owner.address, prompt, aigcData, uri, validProof); + await expect(erc7007.mint(owner.address, 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)) + const [owner] = await ethers.getSigners(); + await expect(erc7007.mint(owner.address, prompt, aigcData, uri, validProof)) .to.emit(erc7007, "Mint") }); }); @@ -59,8 +57,9 @@ describe("ERC7007.sol", function () { 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"}'); + const [owner] = await ethers.getSigners(); + await erc7007.mint(owner.address, prompt, aigcData, uri, validProof); + expect(await erc7007.tokenURI(tokenId)).to.equal('{"name": "test", "description": "test", "image": "test", "aigc_type": "test", "proof_type": "test", "prompt": "test", "aigc_data": "test"}'); }); }); }); @@ -69,11 +68,13 @@ describe("ERC7007Enumerable.sol", function () { async function deployERC7007EnumerableFixture() { const verifier = await deployVerifierFixture(); + const [owner] = await ethers.getSigners(); 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); + + await erc7007Enumerable.mint(owner.address, prompt, aigcData, uri, validProof); return erc7007Enumerable; } @@ -87,4 +88,62 @@ describe("ERC7007Enumerable.sol", function () { expect(await erc7007Enumerable.prompt(tokenId)).to.equal("test"); }); +}); + +async function deployOpmlLibFixture() { + const OpmlLib = await ethers.getContractFactory("MockOpmlLib"); + const opmlLib = await OpmlLib.deploy(); + await opmlLib.deployed(); + return opmlLib; +} + +describe("ERC7007Opml.sol", function () { + + async function deployERC7007Fixture() { + const opmlLib = await deployOpmlLibFixture(); + + const ERC7007 = await ethers.getContractFactory("ERC7007Opml"); + const erc7007 = await ERC7007.deploy("testing", "TEST", opmlLib.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(owner.address, prompt, aigcData, uri, 0x00); + expect(await erc7007.balanceOf(owner.address)).to.equal(1); + }); + + it("should verify a fianlized request", async function () { + const erc7007 = await deployERC7007Fixture(); + const [owner] = await ethers.getSigners(); + await erc7007.mint(owner.address, prompt, aigcData, uri, 0x00); + expect(await erc7007.verify(prompt, aigcData, 0x00)).to.equal(true); + }); + + it("should not mint a token with same data twice", async function () { + const erc7007 = await deployERC7007Fixture(); + const [owner] = await ethers.getSigners(); + await erc7007.mint(owner.address, prompt, aigcData, uri, 0x00); + await expect(erc7007.mint(owner.address, prompt, aigcData, uri, 0x00)).to.be.revertedWith("ERC721: token already minted"); + }); + + it("should emit a Mint event", async function () { + const erc7007 = await deployERC7007Fixture(); + const [owner] = await ethers.getSigners(); + await expect(erc7007.mint(owner.address, prompt, aigcData, uri, validProof)) + .to.emit(erc7007, "Mint") + }); + }); + + describe("metadata", function () { + it("should return token metadata", async function () { + const erc7007 = await deployERC7007Fixture(); + const [owner] = await ethers.getSigners(); + await erc7007.mint(owner.address, prompt, aigcData, uri, validProof); + expect(await erc7007.tokenURI(tokenId)).to.equal('{"name": "test", "description": "test", "image": "test", "aigc_type": "test", "proof_type": "test", "prompt": "test", "aigc_data": "test"}'); + }); + }); }); \ No newline at end of file diff --git a/assets/erc-7007/workflow.png b/assets/erc-7007/workflow.png new file mode 100644 index 0000000000..ec024bf9ea Binary files /dev/null and b/assets/erc-7007/workflow.png differ