diff --git a/src/delegation/README.md b/src/delegation/README.md index 6b65baff7..d073231a2 100644 --- a/src/delegation/README.md +++ b/src/delegation/README.md @@ -14,8 +14,7 @@ A Preminter contract validates signatures and executes actions to 1. deploy cont `Preminter`: Executes commands on the 1155 contract factory, and created 1155 contracts Constraints: - * **Contract creation params must be unique** - the combination of creator + metadata uri + name must be unique. The Preminter can only create a single contract for each combination of creator, metadat uri, and name. There must be some sort of validation in the create flow that ensures a contract has not been created with those parameters. - * **For each contract, token parameters must be unique.** The combination of parameters for the token to be created, including metadata uri, max supply, duration, etc **must be unique within each contract.** i.e. a contract cannot have two tokens with the same parameters. This is because we use this combination to ensure that a signature to create the token can only be executed once. An alternative design is to require a unique nonce to be appended to the parameters, which would ensure uniqueness; this would need to be provided by the backend. + * **Contract creation params must be unique** - the combination of creator + metadata uri + name must be unique. The Preminter can only create a single contract for each combination of creator, metadat uri, and name, as that combination is used to determinstically determine the contract address. Functions: * `premint`: takes an [EIP712 signature](https://eips.ethereum.org/EIPS/eip-712) created by a creator, contract and token creation params, and creates a contract if the contract doesn’t exist and creates a new token, or creates a new token on an existing contract if it exists. It then mints a specified quantity of tokens to the executor as a reward. These parameters are the same both if a new contract is created or a token is created on an existing contract. The signature must have been previously created from a hash built from all of the input parameters; the hash can be generated using `premintHashData`. **Each signature can only be executed against once**; this is enforced through uniqueness of the contract creation params, the token creation params, and quantity to mint. @@ -41,17 +40,11 @@ Functions: Creating a new contract + token: ![Preminter creation flow](../../uml/generated/gasslessCreate-creation-sequence.svg) -![Preminter creation flow](../../uml/generated/gasslessCreate-creation-activity.svg) Collecting: ![Preminter collection flow](../../uml/generated/gasslessCreate-collecting-sequence.svg) -![Preminter collection flow](../../uml/generated/gasslessCreate-collecting-activity.svg) -* In the front-end a creator creates a signature for contract and token creation. The signature is created off-chain by the creator's account on a hash of the above said parameters. It there are additional tokens to be created, signatures are created for each token to be created. There must be some validation that a signature with the same parameters has not already been created (see constraints above). This can be done by checking against the uniqueness of the created signature. -* Once the creator has signed the message, a backend service (another db or blockchain) must store these signatures which can be retreived later by a collector. This backend must store both the contract + token creation parameters and the signature. -* A collector lands on a page that loads the signature and contract creation params based on the bytes32 signature. The contract + token creation parameters and signature are loaded from the backend service or a subgraph which loads the previously stored signature. -* The collector account executs the function `premint`, passing the corresponding signature and contract creation params. If the contract has not been created, it is created. A new token is created on that contract, and `quantityToMint` tokens are minted to the executor. ## Additional caveats diff --git a/src/nft/ZoraCreator1155Impl.sol b/src/nft/ZoraCreator1155Impl.sol index be267bc3b..d32e69f59 100644 --- a/src/nft/ZoraCreator1155Impl.sol +++ b/src/nft/ZoraCreator1155Impl.sol @@ -795,6 +795,10 @@ contract ZoraCreator1155Impl is return _getImplementation(); } + /// Sets up a new token using a token configuration and a signature created for the token creation parameters. + /// The signature must be created by an account with the PERMISSION_BIT_MINTER role on the contract. + /// @param premintConfig configuration of token to be created + /// @param signature EIP-712 Signature created on the premintConfig by an account with the PERMISSION_BIT_MINTER role on the contract. function delegateSetupNewToken(PremintConfig calldata premintConfig, bytes calldata signature) public nonReentrant returns (uint256 newTokenId) { // if a token has already been created for a premint config with this uid: if (delegatedTokenId[premintConfig.uid] != 0) { diff --git a/test/factory/ZoraCreator1155Factory_Fork.t.sol b/test/factory/ZoraCreator1155Factory_Fork.t.sol index 82a083834..921af2e29 100644 --- a/test/factory/ZoraCreator1155Factory_Fork.t.sol +++ b/test/factory/ZoraCreator1155Factory_Fork.t.sol @@ -15,11 +15,8 @@ import {ZoraCreatorFixedPriceSaleStrategy} from "../../src/minters/fixed-price/Z import {ForkDeploymentConfig} from "../../src/deployment/DeploymentConfig.sol"; contract ZoraCreator1155FactoryForkTest is ForkDeploymentConfig, Test { - uint96 constant tokenPrice = 1 ether; uint256 constant quantityToMint = 3; uint256 constant tokenMaxSupply = 100; - uint32 constant royaltyMintSchedule = 10; - uint32 constant royaltyBPS = 100; address collector; address creator; @@ -96,6 +93,7 @@ contract ZoraCreator1155FactoryForkTest is ForkDeploymentConfig, Test { } function testTheFork(string memory chainName) private { + uint96 tokenPrice = 1 ether; console.log("testing on fork: ", chainName); // create and select the fork, which will be used for all subsequent calls diff --git a/uml/gasslessCreate-collecting-activity.puml b/uml/gasslessCreate-collecting-activity.puml deleted file mode 100644 index c0d41f3b5..000000000 --- a/uml/gasslessCreate-collecting-activity.puml +++ /dev/null @@ -1,20 +0,0 @@ -@startuml - -title Collecting with a premint signature - -start - -:Load signature by\ncontract hash & uid; -if (sig with contract hash + uid\nalready executed) then (yes) - :Redirect to\nstandard mint page; - :Mint on erc1155 contract; - stop -else (no) - if (contract already created) then (yes) - :Show contract address; - endif - :Mint on premint contract. Submit:\ncontract & token params, uid, signature\nquantity, comment; -endif -stop - -@enduml diff --git a/uml/gasslessCreate-collecting-sequence.puml b/uml/gasslessCreate-collecting-sequence.puml index 2eccc4c56..869ae1fea 100644 --- a/uml/gasslessCreate-collecting-sequence.puml +++ b/uml/gasslessCreate-collecting-sequence.puml @@ -1,46 +1,38 @@ @startuml actor Collector -entity PremintCollectPage +entity CollectUI +entity Wallet boundary SignatureAPI -entity SignatureDB -entity PreminterContract +entity PremintExecutorContract entity 1155FactoryContract entity 1155Contract -Collector -> PremintCollectPage: Open, param is \ndeterministic collection address\n+ token uid -Activate PremintCollectPage -PremintCollectPage -> SignatureAPI: Fetch by collection address\n+ token uid -SignatureAPI -> SignatureDB: Fetch most recent signature\nby contract hash token uid -SignatureDB --> SignatureAPI: contract + token creation params\n+ signature -SignatureAPI --> PremintCollectPage: contract + token creation params\n+ signature -PremintCollectPage -> PreminterContract: Check if signature has been used (by contract hash + token uid) -PreminterContract --> PremintCollectPage: Signature has been used or not +Collector -> CollectUI: Open, param is \ndeterministic collection address\n+ token uid +Activate CollectUI +CollectUI -> SignatureAPI: Fetch by:\ncollection address, premint uid +SignatureAPI --> CollectUI: contract creation params,\ntoken creation params,\nsignature -Group signature has been used - - PremintCollectPage -> Collector: Redirect to \nstandard collect page - -end - -Collector -> PremintCollectPage: mint -PremintCollectPage -> Collector: Submit transaction -deactivate PremintCollectPage -Collector -> PreminterContract: Submit premint transaction containing \nsignature, contract creation & token creation params -activate PreminterContract -PreminterContract -> PreminterContract: record signature used;\nrevert if already used +Collector -> CollectUI: mint +CollectUI -> Wallet: Submit premint transaction +deactivate CollectUI +Wallet -> PremintExecutorContract: premint(collectionConfig, tokenConfig, uid, signature) +activate PremintExecutorContract Group contract doesnt exist - PreminterContract -> 1155FactoryContract: create contract + PremintExecutorContract -> 1155FactoryContract: create contract + activate 1155FactoryContract 1155FactoryContract -> 1155Contract: create + deactivate 1155FactoryContract activate 1155Contract end PreminterContract -> 1155Contract: create new token\nwith signature +PreminterContract -> 1155Contract: set new token sale parameters PreminterContract -> 1155Contract: mint tokens to collector -deactivate PreminterContract +deactivate PremintExecutorContract 1155Contract --> Collector: Minted tokens deactivate 1155Contract diff --git a/uml/gasslessCreate-creation-activity.puml b/uml/gasslessCreate-creation-activity.puml deleted file mode 100644 index 363927823..000000000 --- a/uml/gasslessCreate-creation-activity.puml +++ /dev/null @@ -1,27 +0,0 @@ -@startuml - -title Creating a token signature - -start - -if (new token) then (yes) - if (new contract) then (yes) - :Ask creator for new\ncontract creation params; - if (contract exists\nwith same params) then (yes) - :switch ui to create token\non existing contract; - else (no) - endif - else (no) - :load existing\ncontract creation parameters\nby collection address; - endif - :Get new uid\nfrom backend server; -else (no) - :load existing\ncontract + token creation parameters\nby collection address + uid; -endif -:Ask creator for new\ntoken creation params; -:Request signature with:\ncollection address + token params + uid; -:Submit to backend server:\ncollection + token params + uid + signature\n; - -stop - -@enduml diff --git a/uml/gasslessCreate-creation-sequence.puml b/uml/gasslessCreate-creation-sequence.puml index 1d7213f43..37ea85acc 100644 --- a/uml/gasslessCreate-creation-sequence.puml +++ b/uml/gasslessCreate-creation-sequence.puml @@ -1,51 +1,60 @@ @startuml -title Creating a signature for a new erc1155 contract + token +title Creating a signature for a Premint Erc1155 contract + New token actor Creator -entity CreatePage -boundary SignatureAPI +entity Wallet +entity CreateUI +boundary PremintAPI boundary PremintContract -entity SignatureDB - -Group Signature not created for contract yet +Group New premint token on new contract - activate CreatePage - Creator -> CreatePage: setup NEW contract name + image - CreatePage -> SignatureAPI: validate that contract \nwith same params for\ncreator doesnt exist - SignatureAPI -> SignatureDB: check if signature with hash \nfor contract is already stored - SignatureAPI --> CreatePage: validation results + Creator -> CreateUI: setup NEW contract name + image + activate CreateUI + CreateUI -> PremintContract: get determnistic collection address\nfor contract creation params + activate PremintContract + PremintContract --> CreateUI: determinstic collection address + deactivate CreateUI + deactivate PremintContract end -Group Signature has been created for contract +Group New premint token on existing premint contract + + Creator -> CreateUI: load page to create new token for\npremint at determinstic\ncollection address + activate CreateUI + CreateUI -> PremintAPI: load collection creation params\nby determinstic address + activate PremintAPI - Creator -> CreatePage: load page by determinstic collection address - CreatePage -> SignatureAPI: load collection creation params - SignatureAPI -> SignatureDB: fetch collection creation params\nby hash - SignatureAPI --> CreatePage: contract creation params + Group Premint exists + PremintAPI --> CreateUI: collection creation params\n(from premint) + deactivate CreateUI + deactivate PremintAPI + end end -Creator -> CreatePage: setup new token -CreatePage -> PremintContract: get determnistic collection address -PremintContract --> CreatePage: determinstic collection address -CreatePage -> SignatureAPI: get new uid for collection address -SignatureAPI -> SignatureDB: get next token uid\nscoped to collection address -SignatureDB --> SignatureAPI: next token uid -SignatureAPI --> CreatePage: next token uid -Creator -> CreatePage: Submit new token creation params -CreatePage -> Creator: request signature of\n contract + token creation params + token uid -deactivate CreatePage -Creator -> SignatureAPI: Submit signature + contract + token params + token uid -SignatureAPI -> PremintContract: validate signature -PremintContract --> SignatureAPI: validation results (true/false & recovered signer) +CreateUI -> PremintAPI: get next uid for\ncollection address +activate CreateUI +activate PremintAPI +PremintAPI --> CreateUI: next uid for collection -Group Signature is valid +Creator -> CreateUI: configure new token parameters +Creator -> CreateUI: Submit +CreateUI -> Creator: request Premint EIP-712 signature containing:\n token creation params, token uid, version +Creator -> Wallet: sign message +Wallet -> CreateUI: Signed message by creator of\ntoken creation params, token uid, version +CreateUI -> PremintAPI: validate and store signature +PremintAPI -> PremintContract: validate signature +PremintContract --> PremintAPI: validation results (true/false & recovered signer) - SignatureAPI -> SignatureDB: store signature + \ncontract creation + \ntoken creation params + \ncollection address + \ntoken uid +Group Signature is valid + PremintAPI -> PremintAPI: store premint and signature end +PremintAPI -> CreateUI: validation & storage status +deactivate CreateUI + @enduml \ No newline at end of file diff --git a/uml/generated/gasslessCreate-collecting-activity.svg b/uml/generated/gasslessCreate-collecting-activity.svg deleted file mode 100644 index 141d18db0..000000000 --- a/uml/generated/gasslessCreate-collecting-activity.svg +++ /dev/null @@ -1 +0,0 @@ -Collecting with a premint signatureLoad signature bycontract hash & uidsig with contract hash + uidalready executedyesnoRedirect tostandard mint pageMint on erc1155 contractShow contract addressyescontract already createdMint on premint contract. Submit:contract & token params, uid, signaturequantity, comment \ No newline at end of file diff --git a/uml/generated/gasslessCreate-collecting-sequence.svg b/uml/generated/gasslessCreate-collecting-sequence.svg index 0115a8fed..06c41255c 100644 --- a/uml/generated/gasslessCreate-collecting-sequence.svg +++ b/uml/generated/gasslessCreate-collecting-sequence.svg @@ -1 +1 @@ -CollectorCollectorPremintCollectPagePremintCollectPageSignatureAPISignatureAPISignatureDBSignatureDBPreminterContractPreminterContract1155FactoryContract1155FactoryContract1155Contract1155ContractOpen, param isdeterministic collection address+ token uidFetch by collection address+ token uidFetch most recent signatureby contract hash token uidcontract + token creation params+ signaturecontract + token creation params+ signatureCheck if signature has been used (by contract hash + token uid)Signature has been used or notsignature has been usedRedirect tostandard collect pagemintSubmit transactionSubmit premint transaction containingsignature, contract creation & token creation paramsrecord signature used;revert if already usedcontract doesnt existcreate contractcreatecreate new tokenwith signaturemint tokens to collectorMinted tokens \ No newline at end of file +CollectorCollectorCollectUICollectUIWalletWalletSignatureAPISignatureAPIPremintExecutorContractPremintExecutorContract1155FactoryContract1155FactoryContract1155Contract1155ContractPreminterContractPreminterContractOpen, param isdeterministic collection address+ token uidFetch by:collection address, premint uidcontract creation params,token creation params,signaturemintSubmit premint transactionpremint(collectionConfig, tokenConfig, uid, signature)contract doesnt existcreate contractcreatecreate new tokenwith signatureset new token sale parametersmint tokens to collectorMinted tokens \ No newline at end of file diff --git a/uml/generated/gasslessCreate-creation-activity.svg b/uml/generated/gasslessCreate-creation-activity.svg deleted file mode 100644 index b0e6d67cc..000000000 --- a/uml/generated/gasslessCreate-creation-activity.svg +++ /dev/null @@ -1 +0,0 @@ -Creating a token signaturenew tokenyesnonew contractyesnoAsk creator for newcontract creation paramsswitch ui to create tokenon existing contractyescontract existswith same paramsnoload existingcontract creation parametersby collection addressGet new uidfrom backend serverload existingcontract + token creation parametersby collection address + uidAsk creator for newtoken creation paramsRequest signature with:collection address + token params + uidSubmit to backend server:collection + token params + uid + signature  \ No newline at end of file diff --git a/uml/generated/gasslessCreate-creation-sequence.svg b/uml/generated/gasslessCreate-creation-sequence.svg index 4d2fbdad9..920596d75 100644 --- a/uml/generated/gasslessCreate-creation-sequence.svg +++ b/uml/generated/gasslessCreate-creation-sequence.svg @@ -1 +1 @@ -Creating a signature for a new erc1155 contract + tokenCreatorCreatorCreatePageCreatePageSignatureAPISignatureAPIPremintContractPremintContractSignatureDBSignatureDBSignature not created for contract yetsetup NEW contract name + imagevalidate that contractwith same params forcreator doesnt existcheck if signature with hashfor contract is already storedvalidation resultsSignature has been created for contractload page by determinstic collection addressload collection creation paramsfetch collection creation paramsby hashcontract creation paramssetup new tokenget determnistic collection addressdeterminstic collection addressget new uid for collection addressget next token uidscoped to collection addressnext token uidnext token uidSubmit new token creation paramsrequest signature ofcontract + token creation params + token uidSubmit signature + contract + token params + token uidvalidate signaturevalidation results (true/false & recovered signer)Signature is validstore signature +contract creation +token creation params +collection address +token uid \ No newline at end of file +Creating a signature for a Premint Erc1155 contract + New tokenCreatorCreatorWalletWalletCreateUICreateUIPremintAPIPremintAPIPremintContractPremintContractNew premint token on new contractsetup NEW contract name + imageget determnistic collection addressfor contract creation paramsdeterminstic collection addressNew premint token on existing premint contractload page to create new token forpremint at determinsticcollection addressload collection creation paramsby determinstic addressPremint existscollection creation params(from premint)get next uid forcollection addressnext uid for collectionconfigure new token parametersSubmitrequest Premint EIP-712 signature containing:token creation params, token uid, versionsign messageSigned message by creator oftoken creation params, token uid, versionvalidate and store signaturevalidate signaturevalidation results (true/false & recovered signer)Signature is validstore premint and signaturevalidation & storage status \ No newline at end of file