Skip to content

Commit

Permalink
moved uid out of the token cofnig
Browse files Browse the repository at this point in the history
  • Loading branch information
oveddan committed Jun 13, 2023
1 parent 9d8e204 commit 211d654
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 45 deletions.
6 changes: 3 additions & 3 deletions addresses/999.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"FACTORY_PROXY": "0x6a357139C1bcDcf0B3AB9bC447932dDdcb956703",
"FIXED_PRICE_SALE_STRATEGY": "0xd81351363b7d80b06E4Ec4De7989f0f91e41A846",
"MERKLE_MINT_SALE_STRATEGY": "0x2c4457D38A329526063b26a2bB2C31B61553Aa98",
"PREMINTER": "0xAa2c4F9e2EB8c94bcf12771bb09f7B1cc8486D12",
"PREMINTER": "0x02be886A3b2802177181f4734380CB1f4BaC4Bfb",
"REDEEM_MINTER_FACTORY": "0x27817bAef1341De9Ad04097Bbba4Ea8dA32c8552",
"timestamp": 1686690340,
"commit": "33d7642"
"timestamp": 1686691999,
"commit": "80ec7f9"
}
16 changes: 12 additions & 4 deletions package/preminter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,16 @@ describe("ZoraCreator1155Preminter", () => {
royaltyMintSchedule: 30,
royaltyBPS: 200,
royaltyRecipient: creatorAccount,
uid: 1n,
};

const uid = 105n;

const signedMessage = await walletClient.signTypedData({
...preminterTypedDataDefinition({
preminterAddress,
chainId: foundry.id,
contractConfig,
uid,
tokenConfig,
}),
account: creatorAccount,
Expand All @@ -139,7 +141,7 @@ describe("ZoraCreator1155Preminter", () => {
abi: preminterAbi,
address: preminterAddress,
functionName: "recoverSigner",
args: [contractConfig, tokenConfig, signedMessage],
args: [contractConfig, tokenConfig, uid, signedMessage],
});

expect(recoveredAddress).to.equal(creatorAccount);
Expand All @@ -155,6 +157,8 @@ describe("ZoraCreator1155Preminter", () => {
contractName: "My fun NFT",
};

const uid = 1n;

const tokenConfig: TokenCreationConfig = {
tokenURI: "ipfs://tokenIpfsId0",
maxSupply: 100n,
Expand All @@ -164,7 +168,6 @@ describe("ZoraCreator1155Preminter", () => {
royaltyMintSchedule: 30,
royaltyBPS: 200,
royaltyRecipient: creatorAccount,
uid: 1n,
};

// have creator sign the message to create the contract
Expand All @@ -175,6 +178,7 @@ describe("ZoraCreator1155Preminter", () => {
chainId: foundry.id,
contractConfig,
tokenConfig,
uid
}),
// signer account is the creator
account: creatorAccount,
Expand All @@ -200,6 +204,7 @@ describe("ZoraCreator1155Preminter", () => {
args: [
contractConfig,
tokenConfig,
uid,
signedMessage,
quantityToMint,
comment,
Expand Down Expand Up @@ -239,13 +244,14 @@ describe("ZoraCreator1155Preminter", () => {
// get token balance - should be amount that was created
expect(tokenBalance).toBe(quantityToMint);

const uid2 = uid + 1n;

// create a signature to create a second token,
// with different ipfs url and price per token
const tokenConfig2: TokenCreationConfig = {
...tokenConfig,
tokenURI: "ipfs://tokenIpfsId2",
pricePerToken: parseEther("0.05"),
uid: 2n,
};

const signedMessage2 = await walletClient.signTypedData({
Expand All @@ -254,6 +260,7 @@ describe("ZoraCreator1155Preminter", () => {
chainId: foundry.id,
contractConfig,
tokenConfig: tokenConfig2,
uid: uid2
}),
account: creatorAccount,
});
Expand All @@ -272,6 +279,7 @@ describe("ZoraCreator1155Preminter", () => {
args: [
contractConfig,
tokenConfig2,
uid2,
signedMessage2,
quantityToMint2,
comment,
Expand Down
5 changes: 4 additions & 1 deletion package/preminter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,20 @@ export const preminterTypedDataDefinition = ({
preminterAddress,
contractConfig,
tokenConfig,
uid,
chainId,
}: {
preminterAddress: Address;
contractConfig: ContractCreationConfig;
tokenConfig: TokenCreationConfig;
uid: bigint,
chainId: number;
}) => {
const types = {
ContractAndToken: [
{ name: "contractConfig", type: "ContractCreationConfig" },
{ name: "tokenConfig", type: "TokenCreationConfig" },
{ name: 'uid', type: 'uint256'}
],
ContractCreationConfig: [
{ name: "contractAdmin", type: "address" },
Expand All @@ -46,7 +49,6 @@ export const preminterTypedDataDefinition = ({
{ name: "royaltyMintSchedule", type: "uint32" },
{ name: "royaltyBPS", type: "uint32" },
{ name: "royaltyRecipient", type: "address" },
{ name: "uid", type: "uint256" },
],
};

Expand All @@ -61,6 +63,7 @@ export const preminterTypedDataDefinition = ({
message: {
contractConfig,
tokenConfig,
uid
},
primaryType: "ContractAndToken",
};
Expand Down
8 changes: 4 additions & 4 deletions script/EstimatePreminterGas.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ contract EstimatePreminterGas is ZoraDeployerBase {
saleDuration: 365 days,
royaltyBPS: 10,
royaltyRecipient: deployer,
royaltyMintSchedule: 20,
uid: 1
royaltyMintSchedule: 20
});
// how many tokens are minted to the executor
uint256 quantityToMint = 1;
uint256 uid = 100;

uint256 valueToSend = quantityToMint * ZoraCreator1155Impl(address(factory.implementation())).mintFee();

bytes32 digest = preminter.premintHashData(contractConfig, tokenConfig, chainId());
bytes32 digest = preminter.premintHashData(contractConfig, tokenConfig, uid, chainId());

uint256 privateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");

Expand All @@ -75,7 +75,7 @@ contract EstimatePreminterGas is ZoraDeployerBase {
// now do an on-chain premint
vm.startBroadcast(deployer);

preminter.premint{value: valueToSend}(contractConfig, tokenConfig, signature, quantityToMint, comment);
preminter.premint{value: valueToSend}(contractConfig, tokenConfig, uid, signature, quantityToMint, comment);

vm.stopBroadcast();
}
Expand Down
39 changes: 23 additions & 16 deletions src/premint/ZoraCreator1155Preminter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId {
uint32 royaltyBPS;
/// @notice RoyaltyRecipient for created tokens. The address that will receive the royalty payments.
address royaltyRecipient;
/// @notice Unique id of the token, used to ensure that multiple signatures can't be used to create the same intended token, in the case
/// that a signature is updated for a token, and the old signature is executed, two tokens for the same original intended token could be created.
/// Only one signature per token id, scoped to the contract hash can be executed.
uint256 uid;
}

event Preminted(
Expand All @@ -95,6 +91,10 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId {
function premint(
ContractCreationConfig calldata contractConfig,
TokenCreationConfig calldata tokenConfig,
/// @notice Unique id of the token, used to ensure that multiple signatures can't be used to create the same intended token, in the case
/// that a signature is updated for a token, and the old signature is executed, two tokens for the same original intended token could be created.
/// Only one signature per token id, scoped to the contract hash can be executed.
uint256 uid,
bytes calldata signature,
uint256 quantityToMint,
string calldata mintComment
Expand All @@ -113,12 +113,12 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId {

// validate the signature for the current chain id, and make sure it hasn't been used, marking
// that it has been used
_validateSignatureAndEnsureNotUsed(contractConfig, tokenConfig, signature);
_validateSignatureAndEnsureNotUsed(contractConfig, tokenConfig, uid, signature);

// setup the new token, and its sales config
newTokenId = _setupNewTokenAndSale(tokenContract, contractConfig.contractAdmin, tokenConfig);

emit Preminted(contractAddress, newTokenId, isNewContract, contractHash, tokenConfig.uid, contractConfig, tokenConfig, msg.sender, quantityToMint);
emit Preminted(contractAddress, newTokenId, isNewContract, contractHash, uid, contractConfig, tokenConfig, msg.sender, quantityToMint);

// mint the initial x tokens for this new token id to the executor.
address tokenRecipient = msg.sender;
Expand Down Expand Up @@ -239,12 +239,14 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId {
function recoverSigner(
ContractCreationConfig calldata contractConfig,
TokenCreationConfig calldata tokenConfig,
uint256 uid,
bytes calldata signature
) public view returns (address signatory) {
// first validate the signature - the creator must match the signer of the message
bytes32 digest = premintHashData(
contractConfig,
tokenConfig,
uid,
// here we pass the current chain id, ensuring that the message
// only works for the current chain id
block.chainid
Expand All @@ -256,9 +258,10 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId {
function premintHashData(
ContractCreationConfig calldata contractConfig,
TokenCreationConfig calldata tokenConfig,
uint256 uid,
uint256 chainId
) public view returns (bytes32) {
bytes32 encoded = _hashContractAndToken(contractConfig, tokenConfig);
bytes32 encoded = _hashContractAndToken(contractConfig, tokenConfig, uid);

// build the struct hash to be signed
// here we pass the chain id, allowing the message to be signed for another chain
Expand All @@ -267,16 +270,20 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId {

bytes32 constant CONTRACT_AND_TOKEN_DOMAIN =
keccak256(
"ContractAndToken(ContractCreationConfig contractConfig,TokenCreationConfig tokenConfig)ContractCreationConfig(address contractAdmin,string contractURI,string contractName)TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 saleDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient,uint256 uid)"
"ContractAndToken(ContractCreationConfig contractConfig,TokenCreationConfig tokenConfig,uint256 uid)ContractCreationConfig(address contractAdmin,string contractURI,string contractName)TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 saleDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient)"
);

function _hashContractAndToken(ContractCreationConfig calldata contractConfig, TokenCreationConfig calldata tokenConfig) private pure returns (bytes32) {
return keccak256(abi.encode(CONTRACT_AND_TOKEN_DOMAIN, _hashContract(contractConfig), _hashToken(tokenConfig)));
function _hashContractAndToken(
ContractCreationConfig calldata contractConfig,
TokenCreationConfig calldata tokenConfig,
uint256 uid
) private pure returns (bytes32) {
return keccak256(abi.encode(CONTRACT_AND_TOKEN_DOMAIN, _hashContract(contractConfig), _hashToken(tokenConfig), uid));
}

bytes32 constant TOKEN_DOMAIN =
keccak256(
"TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 saleDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient,uint256 uid)"
"TokenCreationConfig(string tokenURI,uint256 maxSupply,uint64 maxTokensPerAddress,uint96 pricePerToken,uint64 saleDuration,uint32 royaltyMintSchedule,uint32 royaltyBPS,address royaltyRecipient)"
);

function _hashToken(TokenCreationConfig calldata tokenConfig) private pure returns (bytes32) {
Expand All @@ -291,8 +298,7 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId {
tokenConfig.saleDuration,
tokenConfig.royaltyMintSchedule,
tokenConfig.royaltyBPS,
tokenConfig.royaltyRecipient,
tokenConfig.uid
tokenConfig.royaltyRecipient
)
);
}
Expand Down Expand Up @@ -320,20 +326,21 @@ contract ZoraCreator1155Preminter is EIP712UpgradeableWithChainId {
function _validateSignatureAndEnsureNotUsed(
ContractCreationConfig calldata contractConfig,
TokenCreationConfig calldata tokenConfig,
uint256 uid,
bytes calldata signature
) private returns (uint256 tokenHash) {
// first validate the signature - the creator must match the signer of the message
address signatory = recoverSigner(contractConfig, tokenConfig, signature);
address signatory = recoverSigner(contractConfig, tokenConfig, uid, signature);

if (signatory != contractConfig.contractAdmin) {
revert("Invalid signature");
}

// make sure that this signature hasn't been used
// token hash includes the contract hash, so we can check uniqueness of contract + token pair
tokenHash = contractAndTokenHash(contractConfig, tokenConfig.uid);
tokenHash = contractAndTokenHash(contractConfig, uid);
if (tokenCreated[tokenHash]) {
revert TokenAlreadyCreated(contractConfig.contractAdmin, contractConfig.contractURI, contractConfig.contractName, tokenConfig.uid);
revert TokenAlreadyCreated(contractConfig.contractAdmin, contractConfig.contractURI, contractConfig.contractName, uid);
}
tokenCreated[tokenHash] = true;

Expand Down
Loading

0 comments on commit 211d654

Please sign in to comment.