Skip to content

Commit

Permalink
Changed premint executor to use simpler method for determining if sig…
Browse files Browse the repository at this point in the history
…ner is authorized to sign a premint (#344)

* Simplified authorization determining methods for premint;
Instead of having a bunch of methods that need to decode a signature and determine if a creator is authorized to create premints, just have a single method that determines if the creator is authorized, and put the burden on external clients to decode the signature.

* added new premint sdk method to replace existing `isAuthorizedToSign` function that existed on the contract`
  • Loading branch information
oveddan authored and kulkarohan committed Dec 8, 2023
1 parent 36eea20 commit 43ffa60
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 91 deletions.
13 changes: 11 additions & 2 deletions .changeset/violet-starfishes-visit.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ struct TokenCreationConfig {
* new function `premintV1` - takes a `PremintConfig`, and premint v1 signature, and executes a premint, with added functionality of being able to specify mint referral and mint recipient
* new function `premintV2` - takes a `PremintConfigV2` signature and executes a premint, with being able to specify mint referral and mint recipient
* deprecated function `premint` - call `premintV1` instead
* new function `isValidSignatureV1` - takes an 1155 address, contract admin, premint v1 config and signature, and validates the signature. Can be used for 1155 contracts that were not created via the premint executor contract.
* new function `isValidSignatureV2` - takes an 1155 address, contract admin, premint v2 config and signature, and validates the signature. Can be used for 1155 contracts that were not created via the premint executor contract.
* new function

```solidity
isAuthorizedToCreatePremint(
address signer,
address premintContractConfigContractAdmin,
address contractAddress
) public view returns (bool isAuthorized)
```

takes a signer, contractConfig.contractAdmin, and 1155 address, and determines if the signer is authorized to sign premints on the given contract. Replaces `isValidSignature` - by putting the burden on clients to first decode the signature, then pass the recovered signer to this function to determine if the signer has premint authorization on the contract.
* deprecated function `isValidSignature` - call `isValidSignatureV1` instead
5 changes: 5 additions & 0 deletions .changeset/wicked-dolphins-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zoralabs/premint-sdk": patch
---

`preminter` exposes new function isValidSignatureV1 that recovers a signer from a signed premint and determines if that signer is authorized to sign
Original file line number Diff line number Diff line change
Expand Up @@ -234,25 +234,17 @@ library ZoraCreator1155Attribution {
/// @dev copied from ZoraCreator1155Impl
uint256 constant PERMISSION_BIT_MINTER = 2 ** 2;

function isValidSignature(
address originalPremintCreator,
address contractAddress,
bytes32 structHash,
bytes32 hashedVersion,
bytes calldata signature
) internal view returns (bool isValid, address recoveredSigner) {
recoveredSigner = recoverSignerHashed(structHash, signature, contractAddress, hashedVersion, block.chainid);

if (recoveredSigner == address(0)) {
return (false, address(0));
}

// if contract hasn't been created, signer must be the contract admin on the config
function isAuthorizedToCreatePremint(
address signer,
address premintContractConfigContractAdmin,
address contractAddress
) internal view returns (bool authorized) {
// if contract hasn't been created, signer must be the contract admin on the premint config
if (contractAddress.code.length == 0) {
isValid = recoveredSigner == originalPremintCreator;
return signer == premintContractConfigContractAdmin;
} else {
// if contract has been created, signer must have mint new token permission
isValid = IZoraCreator1155(contractAddress).isAdminOrRole(recoveredSigner, CONTRACT_BASE_ID, PERMISSION_BIT_MINTER);
authorized = IZoraCreator1155(contractAddress).isAdminOrRole(signer, CONTRACT_BASE_ID, PERMISSION_BIT_MINTER);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,65 +153,42 @@ contract ZoraCreator1155PremintExecutorImpl is
return (true, ERC1155DelegationStorageV1(contractAddress).delegatedTokenId(uid));
}

// @custom:deprecated use isValidSignatureV1 instead
// @custom:deprecated use isAuthorizedToCreatePremint instead
function isValidSignature(
ContractCreationConfig calldata contractConfig,
PremintConfig calldata premintConfig,
bytes calldata signature
) public view returns (bool isValid, address contractAddress, address recoveredSigner) {
contractAddress = getContractAddress(contractConfig);

(isValid, recoveredSigner) = isValidSignatureV1(contractConfig.contractAdmin, contractAddress, premintConfig, signature);
}

/// @notice Recovers the signer of a premint, and checks if the signer is authorized to sign the premint.
/// @dev for use with v1 of premint config, PremintConfig
/// @param premintContractConfigContractAdmin If this contract was created via premint, the original contractConfig.contractAdmin. Otherwise, set to address(0)
/// @param contractAddress The determinstic 1155 contract address the premint is for
/// @param premintConfig The premint config
/// @param signature The signature of the premint
/// @return isValid Whether the signature is valid
/// @return recoveredSigner The signer of the premint
function isValidSignatureV1(
address premintContractConfigContractAdmin,
address contractAddress,
PremintConfig calldata premintConfig,
bytes calldata signature
) public view returns (bool isValid, address recoveredSigner) {
bytes32 hashedPremint = ZoraCreator1155Attribution.hashPremint(premintConfig);

(isValid, recoveredSigner) = ZoraCreator1155Attribution.isValidSignature(
premintContractConfigContractAdmin,
recoveredSigner = ZoraCreator1155Attribution.recoverSignerHashed(
ZoraCreator1155Attribution.hashPremint(premintConfig),
signature,
contractAddress,
hashedPremint,
ZoraCreator1155Attribution.HASHED_VERSION_1,
signature
block.chainid
);

if (recoveredSigner == address(0)) {
return (false, address(0), recoveredSigner);
}

isValid = isAuthorizedToCreatePremint(recoveredSigner, contractConfig.contractAdmin, contractAddress);
}

/// @notice Recovers the signer of a premint, and checks if the signer is authorized to sign the premint.
/// @dev for use with v2 of premint config, PremintConfig
/// @notice Checks if the signer of a premint is authorized to sign a premint for a given contract. If the contract hasn't been created yet,
/// then the signer is authorized if the signer's address matches contractConfig.contractAdmin. Otherwise, the signer must have the PERMISSION_BIT_MINTER
/// role on the contract
/// @param signer The signer of the premint
/// @param premintContractConfigContractAdmin If this contract was created via premint, the original contractConfig.contractAdmin. Otherwise, set to address(0)
/// @param contractAddress The determinstic 1155 contract address the premint is for
/// @param premintConfig The premint config
/// @param signature The signature of the premint
/// @return isValid Whether the signature is valid
/// @return recoveredSigner The signer of the premint
function isValidSignatureV2(
/// @return isAuthorized Whether the signer is authorized
function isAuthorizedToCreatePremint(
address signer,
address premintContractConfigContractAdmin,
address contractAddress,
PremintConfigV2 calldata premintConfig,
bytes calldata signature
) public view returns (bool isValid, address recoveredSigner) {
bytes32 hashedPremint = ZoraCreator1155Attribution.hashPremint(premintConfig);

(isValid, recoveredSigner) = ZoraCreator1155Attribution.isValidSignature(
premintContractConfigContractAdmin,
contractAddress,
hashedPremint,
ZoraCreator1155Attribution.HASHED_VERSION_2,
signature
);
address contractAddress
) public view returns (bool isAuthorized) {
return ZoraCreator1155Attribution.isAuthorizedToCreatePremint(signer, premintContractConfigContractAdmin, contractAddress);
}

/// @notice Returns the versions of the premint signature that the contract supports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ interface ILegacyZoraCreator1155PremintExecutor {
uint256 quantityToMint,
string calldata mintComment
) external payable returns (uint256 newTokenId);

function isAuthorizedToCreatePremint(
address signer,
address premintContractConfigContractAdmin,
address contractAddress
) external view returns (bool isAuthorized);
}

interface IZoraCreator1155PremintExecutorV1 {
Expand All @@ -37,13 +43,6 @@ interface IZoraCreator1155PremintExecutorV1 {
uint256 quantityToMint,
IZoraCreator1155PremintExecutor.MintArguments calldata mintArguments
) external payable returns (IZoraCreator1155PremintExecutor.PremintResult memory);

function isValidSignatureV1(
address originalContractAdmin,
address contractAddress,
PremintConfig calldata premintConfig,
bytes calldata signature
) external view returns (bool isValid, address recoveredSigner);
}

interface IZoraCreator1155PremintExecutorV2 {
Expand All @@ -54,13 +53,6 @@ interface IZoraCreator1155PremintExecutorV2 {
uint256 quantityToMint,
IZoraCreator1155PremintExecutor.MintArguments calldata mintArguments
) external payable returns (IZoraCreator1155PremintExecutor.PremintResult memory);

function isValidSignatureV2(
address originalContractAdmin,
address contractAddress,
PremintConfigV2 calldata premintConfig,
bytes calldata signature
) external view returns (bool isValid, address recoveredSigner);
}

interface IZoraCreator1155PremintExecutor is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ contract ZoraCreator1155PreminterTest is Test {
assertTrue(isValid);

// now check using new method
(isValid, ) = preminter.isValidSignatureV1(contractConfig.contractAdmin, contractAddress, premintConfig, signature);
isValid = preminter.isAuthorizedToCreatePremint(creator, contractConfig.contractAdmin, contractAddress);
assertTrue(isValid);

// now call the premint function, using the same config that was used to generate the digest, and the signature
Expand Down Expand Up @@ -667,18 +667,13 @@ contract ZoraCreator1155PreminterTest is Test {

address contractAddress = preminter.getContractAddress(contractConfig);

// sign and execute premint
bytes memory signature = _signPremint(contractAddress, premintConfig, creatorPrivateKey, block.chainid);

(bool isValidSignature, address recoveredSigner) = preminter.isValidSignatureV2(
contractConfig.contractAdmin,
contractAddress,
premintConfig,
signature
);
bool isValidSignature = preminter.isAuthorizedToCreatePremint({
signer: creator,
premintContractConfigContractAdmin: contractConfig.contractAdmin,
contractAddress: contractAddress
});

assertEq(creator, recoveredSigner, "recovered the wrong signer");
assertTrue(isValidSignature, "signature should be valid");
assertTrue(isValidSignature, "creator should be allowed to create premint before contract created");

_signAndExecutePremint(contractConfig, premintConfig, creatorPrivateKey, block.chainid, premintExecutor, 1, "hi");

Expand All @@ -694,9 +689,13 @@ contract ZoraCreator1155PreminterTest is Test {
bytes memory newCreatorSignature = _signPremint(contractAddress, premintConfig2, newCreatorPrivateKey, block.chainid);

// it should not be considered a valid signature
(isValidSignature, ) = preminter.isValidSignatureV2(contractConfig.contractAdmin, contractAddress, premintConfig2, newCreatorSignature);
isValidSignature = preminter.isAuthorizedToCreatePremint({
signer: newCreator,
premintContractConfigContractAdmin: contractConfig.contractAdmin,
contractAddress: contractAddress
});

assertFalse(isValidSignature, "signature should not be valid");
assertFalse(isValidSignature, "alternative creator should not be allowed to create a premint");

uint256 quantityToMint = 1;
uint256 mintCost = mintFeeAmount * quantityToMint;
Expand All @@ -712,7 +711,11 @@ contract ZoraCreator1155PreminterTest is Test {
IZoraCreator1155(contractAddress).addPermission(CONTRACT_BASE_ID, newCreator, PERMISSION_BIT_MINTER);

// should now be considered a valid signature
(isValidSignature, ) = preminter.isValidSignatureV2(contractConfig.contractAdmin, contractAddress, premintConfig2, newCreatorSignature);
isValidSignature = preminter.isAuthorizedToCreatePremint({
signer: newCreator,
premintContractConfigContractAdmin: contractConfig.contractAdmin,
contractAddress: contractAddress
});
assertTrue(isValidSignature, "valid signature after granted permission");

vm.deal(premintExecutor, mintCost);
Expand Down
136 changes: 136 additions & 0 deletions packages/protocol-sdk/src/preminter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Address } from "abitype";
import { ExtractAbiFunction, AbiParametersToPrimitiveTypes } from "abitype";
import {
zoraCreator1155PremintExecutorImplABI as preminterAbi,
zoraCreator1155PremintExecutorImplAddress,
} from "@zoralabs/protocol-deployments";
import {
TypedDataDefinition,
recoverTypedDataAddress,
Hex,
PublicClient,
} from "viem";

type PremintInputs = ExtractAbiFunction<
typeof preminterAbi,
"premint"
>["inputs"];

type PreminterHashDataTypes = AbiParametersToPrimitiveTypes<PremintInputs>;

export type ContractCreationConfig = PreminterHashDataTypes[0];
export type PremintConfig = PreminterHashDataTypes[1];
export type TokenCreationConfig = PremintConfig["tokenConfig"];

// Convenience method to create the structured typed data
// needed to sign for a premint contract and token
export const preminterTypedDataDefinition = ({
verifyingContract,
premintConfig,
chainId,
}: {
verifyingContract: Address;
premintConfig: PremintConfig;
chainId: number;
}) => {
const { tokenConfig, uid, version, deleted } = premintConfig;
const types = {
CreatorAttribution: [
{ name: "tokenConfig", type: "TokenCreationConfig" },
// unique id scoped to the contract and token to create.
// ensure that a signature can be replaced, as long as the replacement
// has the same uid, and a newer version.
{ name: "uid", type: "uint32" },
{ name: "version", type: "uint32" },
// if this update should result in the signature being deleted.
{ name: "deleted", type: "bool" },
],
TokenCreationConfig: [
{ name: "tokenURI", type: "string" },
{ name: "maxSupply", type: "uint256" },
{ name: "maxTokensPerAddress", type: "uint64" },
{ name: "pricePerToken", type: "uint96" },
{ name: "mintStart", type: "uint64" },
{ name: "mintDuration", type: "uint64" },
{ name: "royaltyMintSchedule", type: "uint32" },
{ name: "royaltyBPS", type: "uint32" },
{ name: "royaltyRecipient", type: "address" },
{ name: "fixedPriceMinter", type: "address" },
],
};

const result: TypedDataDefinition<typeof types, "CreatorAttribution"> = {
domain: {
chainId,
name: "Preminter",
version: "1",
verifyingContract: verifyingContract,
},
types,
message: {
tokenConfig,
uid,
version,
deleted,
},
primaryType: "CreatorAttribution",
};

return result;
};

export async function isValidSignatureV1({
contractAddress,
originalContractAdmin,
premintConfig,
signature,
chainId,
publicClient,
}: {
contractAddress: Address;
originalContractAdmin: Address;
premintConfig: PremintConfig;
signature: Hex;
chainId: number;
publicClient: PublicClient;
}): Promise<{
isAuthorized: boolean;
recoveredAddress?: Address;
}> {
const typedData = preminterTypedDataDefinition({
verifyingContract: contractAddress,
premintConfig,
chainId,
});

// recover the address from the signature
let recoveredAddress: Address;

try {
recoveredAddress = await recoverTypedDataAddress({
...typedData,
signature,
});
} catch (error) {
console.error(error);

return {
isAuthorized: false,
};
}

// premint executor is same address on all chains
const premintExecutorAddress = zoraCreator1155PremintExecutorImplAddress[999];

const isAuthorized = await publicClient.readContract({
abi: preminterAbi,
address: premintExecutorAddress,
functionName: "isAuthorizedToCreatePremint",
args: [recoveredAddress, originalContractAdmin, contractAddress],
});

return {
isAuthorized,
recoveredAddress,
};
}

0 comments on commit 43ffa60

Please sign in to comment.