Skip to content

Commit

Permalink
Merge branch 'premint' into update_rewards_logic
Browse files Browse the repository at this point in the history
  • Loading branch information
kulkarohan authored Sep 28, 2023
2 parents 1e0d23a + e6b1fb9 commit d1d2d77
Show file tree
Hide file tree
Showing 15 changed files with 79 additions and 123 deletions.
9 changes: 1 addition & 8 deletions src/delegation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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

Expand Down
4 changes: 4 additions & 0 deletions src/nft/ZoraCreator1155Impl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,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) {
Expand Down
12 changes: 8 additions & 4 deletions src/upgrades/UpgradeGate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ import {UpgradeGateStorageV1} from "./UpgradeGateStorageV1.sol";
/// @title UpgradeGate
/// @notice Contract for managing upgrades and safe upgrade paths for 1155 contracts
contract UpgradeGate is IUpgradeGate, Ownable2StepUpgradeable, UpgradeGateStorageV1 {
/// @notice Constructor for deployment pathway
constructor(address _defaultOwner) initializer {
__Ownable_init(_defaultOwner);
/// @notice Constructor for deployment pathway. This contract needs to be atomically initialized to be safe.
constructor() {}

/// @notice Default owner initializer. Allows for shared deterministic addresses.
/// @param _initialOwner initial owner for the contract
function initialize(address _initialOwner) external initializer {
__Ownable_init(_initialOwner);
emit UpgradeGateSetup();
}

Expand All @@ -21,7 +25,7 @@ contract UpgradeGate is IUpgradeGate, Ownable2StepUpgradeable, UpgradeGateStorag

/// @notice The name of the upgrade gate contract
function contractName() external pure returns (string memory) {
return "ZORA 1155 Factory Managed Upgrade Gate";
return "ZORA 1155 Upgrade Gate";
}

/// ///
Expand Down
3 changes: 2 additions & 1 deletion test/factory/ZoraCreator1155Factory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ contract ZoraCreator1155FactoryTest is Test {
zora = makeAddr("zora");
mintFeeAmount = 0.000777 ether;

upgradeGate = new UpgradeGate(zora);
upgradeGate = new UpgradeGate();
upgradeGate.initialize(zora);

address factoryShimAddress = address(new ProxyShim(zora));
Zora1155Factory factoryProxy = new Zora1155Factory(factoryShimAddress, "");
Expand Down
2 changes: 1 addition & 1 deletion test/factory/ZoraCreator1155Factory_Fork.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ 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;
Expand Down Expand Up @@ -94,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
Expand Down
3 changes: 2 additions & 1 deletion test/nft/ZoraCreator1155.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ contract ZoraCreator1155Test is Test {
recipient = vm.addr(0x2);

protocolRewards = new ProtocolRewards();
upgradeGate = new UpgradeGate(admin);
upgradeGate = new UpgradeGate();
upgradeGate.initialize(admin);
zoraCreator1155Impl = new ZoraCreator1155Impl(zora, address(upgradeGate), address(protocolRewards));
target = ZoraCreator1155Impl(address(new Zora1155(address(zoraCreator1155Impl))));
simpleMinter = new SimpleMinter();
Expand Down
3 changes: 2 additions & 1 deletion test/upgrades/UpgradeGate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ contract UpgradeGateTest is Test {
address constant admin = address(0x123);

function setUp() public {
upgradeGate = new UpgradeGate(admin);
upgradeGate = new UpgradeGate();
upgradeGate.initialize(admin);
}

function test_AdminOnly() public {
Expand Down
20 changes: 0 additions & 20 deletions uml/gasslessCreate-collecting-activity.puml

This file was deleted.

42 changes: 17 additions & 25 deletions uml/gasslessCreate-collecting-sequence.puml
Original file line number Diff line number Diff line change
@@ -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

Expand Down
27 changes: 0 additions & 27 deletions uml/gasslessCreate-creation-activity.puml

This file was deleted.

71 changes: 40 additions & 31 deletions uml/gasslessCreate-creation-sequence.puml
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit d1d2d77

Please sign in to comment.