Skip to content

Commit

Permalink
feat: creator reward recipient per token (#278)
Browse files Browse the repository at this point in the history
will deploy and add changeset once we're good on reviews
  • Loading branch information
kulkarohan authored Oct 25, 2023
1 parent 6c83912 commit f00bd85
Show file tree
Hide file tree
Showing 14 changed files with 169 additions and 67 deletions.
5 changes: 5 additions & 0 deletions .changeset/weak-planets-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zoralabs/zora-1155-contracts": minor
---

Creator reward recipient can now be defined on a token by token basis. This allows for multiple creators to collaborate on a contract and each to receive rewards for the token they created. The royaltyRecipient storage field is now used to determine the creator reward recipient for each token. If that's not set for a token, it falls back to use the contract wide fundsRecipient.
1 change: 1 addition & 0 deletions packages/1155-contracts/addresses/1.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"CONTRACT_1155_IMPL": "0x8e90D8cfc0CA66EA143930E4c5F7E31Bf16F722b",
"CONTRACT_1155_IMPL_VERSION": "2.0.0",
"UPGRADE_GATE": "0xbC50029836A59A4E5e1Bb8988272F46ebA0F9900",
"FACTORY_IMPL": "0x55B53DBE22859d538E3b44DD06C9FAE292409E3c",
"FACTORY_PROXY": "0x777777C338d93e2C7adf08D102d45CA7CC4Ed021",
"FIXED_PRICE_SALE_STRATEGY": "0x04E2516A2c207E84a1839755675dfd8eF6302F0a",
Expand Down
1 change: 1 addition & 0 deletions packages/1155-contracts/addresses/10.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"CONTRACT_1155_IMPL": "0xF3a46845548bE811Ce37e65153563f4a0AaEbe31",
"CONTRACT_1155_IMPL_VERSION": "2.0.0",
"UPGRADE_GATE": "0xbC50029836A59A4E5e1Bb8988272F46ebA0F9900",
"FACTORY_IMPL": "0xF7e49F97E82cc38ACd82E303F37Fe046f5a190B5",
"FACTORY_PROXY": "0x777777C338d93e2C7adf08D102d45CA7CC4Ed021",
"FIXED_PRICE_SALE_STRATEGY": "0x3678862f04290E565cCA2EF163BAeb92Bb76790C",
Expand Down
1 change: 1 addition & 0 deletions packages/1155-contracts/addresses/420.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"CONTRACT_1155_IMPL": "0xAF5A4F6F6640734d7D000321Bb27De40D4Ae91f6",
"CONTRACT_1155_IMPL_VERSION": "2.0.0",
"UPGRADE_GATE": "0xbC50029836A59A4E5e1Bb8988272F46ebA0F9900",
"FACTORY_IMPL": "0x7B59c0378F540c0356A5DAEF7574255A7C74EC76",
"FACTORY_PROXY": "0x777777C338d93e2C7adf08D102d45CA7CC4Ed021",
"FIXED_PRICE_SALE_STRATEGY": "0x04E2516A2c207E84a1839755675dfd8eF6302F0a",
Expand Down
1 change: 1 addition & 0 deletions packages/1155-contracts/addresses/5.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"CONTRACT_1155_IMPL": "0x455c9D3188A3Cd94aCDE8E5Ec90cA92FC10805EA",
"CONTRACT_1155_IMPL_VERSION": "2.0.0",
"UPGRADE_GATE": "0xbC50029836A59A4E5e1Bb8988272F46ebA0F9900",
"FACTORY_IMPL": "0x9074Ae399235d26B56e3aF1331b033366E4FE072",
"FACTORY_PROXY": "0x777777C338d93e2C7adf08D102d45CA7CC4Ed021",
"FIXED_PRICE_SALE_STRATEGY": "0x04E2516A2c207E84a1839755675dfd8eF6302F0a",
Expand Down
1 change: 1 addition & 0 deletions packages/1155-contracts/addresses/7777777.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"CONTRACT_1155_IMPL": "0xFc40F9BFE1289F27F89BAEfb4DB97CC2D8eF6a38",
"CONTRACT_1155_IMPL_VERSION": "2.0.0",
"UPGRADE_GATE": "0xbC50029836A59A4E5e1Bb8988272F46ebA0F9900",
"FACTORY_IMPL": "0xC7598f8eAA1455f5b2B3f206A9af55B2BA248e3E",
"FACTORY_PROXY": "0x777777C338d93e2C7adf08D102d45CA7CC4Ed021",
"FIXED_PRICE_SALE_STRATEGY": "0x04E2516A2c207E84a1839755675dfd8eF6302F0a",
Expand Down
1 change: 1 addition & 0 deletions packages/1155-contracts/addresses/8453.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"CONTRACT_1155_IMPL": "0x314E552b55DFbDfD4d76623E1D45E5056723998B",
"CONTRACT_1155_IMPL_VERSION": "2.0.0",
"UPGRADE_GATE": "0xbC50029836A59A4E5e1Bb8988272F46ebA0F9900",
"FACTORY_IMPL": "0xC6899816663891D7493939d74d83cb7f2BBcBB16",
"FACTORY_PROXY": "0x777777C338d93e2C7adf08D102d45CA7CC4Ed021",
"FIXED_PRICE_SALE_STRATEGY": "0x04E2516A2c207E84a1839755675dfd8eF6302F0a",
Expand Down
1 change: 1 addition & 0 deletions packages/1155-contracts/addresses/84531.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"CONTRACT_1155_IMPL": "0xD66B730aA3B4921356Fc56907D22e65CA9F4ff58",
"CONTRACT_1155_IMPL_VERSION": "2.0.0",
"UPGRADE_GATE": "0xbC50029836A59A4E5e1Bb8988272F46ebA0F9900",
"FACTORY_IMPL": "0xF482C51346f3c77673dc619F243Eb8B09E9A954E",
"FACTORY_PROXY": "0x777777C338d93e2C7adf08D102d45CA7CC4Ed021",
"FIXED_PRICE_SALE_STRATEGY": "0x04E2516A2c207E84a1839755675dfd8eF6302F0a",
Expand Down
7 changes: 4 additions & 3 deletions packages/1155-contracts/addresses/999.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"CONTRACT_1155_IMPL": "0x486709A6BeDBD8476d7bCF73F1ab42579A1A7d78",
"CONTRACT_1155_IMPL_VERSION": "2.0.0",
"FACTORY_IMPL": "0x366A36c10E1C851dcfA7804fB313dEA8E3488335",
"CONTRACT_1155_IMPL": "0xCE00c75B9807A2aA87B2297cA7Dc1C0190137D6F",
"CONTRACT_1155_IMPL_VERSION": "2.1.0",
"UPGRADE_GATE": "0xbC50029836A59A4E5e1Bb8988272F46ebA0F9900",
"FACTORY_IMPL": "0x15ba66e376856F3F6FE53dE9eeAb10dEF10E8C92",
"FACTORY_PROXY": "0x777777C338d93e2C7adf08D102d45CA7CC4Ed021",
"FIXED_PRICE_SALE_STRATEGY": "0x04E2516A2c207E84a1839755675dfd8eF6302F0a",
"MERKLE_MINT_SALE_STRATEGY": "0xf48172CA3B6068B20eE4917Eb27b5472f1f272C7",
Expand Down
1 change: 1 addition & 0 deletions packages/1155-contracts/script/ZoraDeployerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ abstract contract ZoraDeployerBase is ScriptDeploymentConfig, DeterministicDeplo
vm.serializeAddress(deploymentJsonKey, FACTORY_IMPL, deployment.factoryImpl);
vm.serializeAddress(deploymentJsonKey, PREMINTER_PROXY, deployment.preminterProxy);
vm.serializeAddress(deploymentJsonKey, PREMINTER_IMPL, deployment.preminterImpl);
vm.serializeAddress(deploymentJsonKey, UPGRADE_GATE, deployment.upgradeGate);
deploymentJson = vm.serializeAddress(deploymentJsonKey, FACTORY_PROXY, deployment.factoryProxy);
console2.log(deploymentJson);
}
Expand Down
25 changes: 19 additions & 6 deletions packages/1155-contracts/src/nft/ZoraCreator1155Impl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ contract ZoraCreator1155Impl is
uint256 ethValueSent = _handleRewardsAndGetValueSent(
msg.value,
quantity,
getCreatorRewardRecipient(),
getCreatorRewardRecipient(tokenId),
createReferrals[tokenId],
address(0),
firstMinters[tokenId]
Expand Down Expand Up @@ -437,7 +437,7 @@ contract ZoraCreator1155Impl is
uint256 ethValueSent = _handleRewardsAndGetValueSent(
msg.value,
quantity,
getCreatorRewardRecipient(),
getCreatorRewardRecipient(tokenId),
createReferrals[tokenId],
mintReferral,
firstMinters[tokenId]
Expand All @@ -453,10 +453,23 @@ contract ZoraCreator1155Impl is
return TOTAL_REWARD_PER_MINT;
}

/// @notice Get the creator reward recipient address
/// @dev The creator is not enforced to set a funds recipient address, so in that case the reward would be claimable by creator's contract
function getCreatorRewardRecipient() public view returns (address payable) {
return config.fundsRecipient != address(0) ? config.fundsRecipient : payable(address(this));
/// @notice Get the creator reward recipient address for a specific token.
/// @param tokenId The token id to get the creator reward recipient for
/// @dev Returns the royalty recipient address for the token if set; otherwise uses the fundsRecipient.
/// If both are not set, this contract will be set as the recipient, and an account with
/// `PERMISSION_BIT_FUNDS_MANAGER` will be able to withdraw via the `withdrawRewards` function.
function getCreatorRewardRecipient(uint256 tokenId) public view returns (address) {
address royaltyRecipient = getRoyalties(tokenId).royaltyRecipient;

if (royaltyRecipient != address(0)) {
return royaltyRecipient;
}

if (config.fundsRecipient != address(0)) {
return config.fundsRecipient;
}

return address(this);
}

/// @notice Set a metadata renderer for a token
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,28 +149,4 @@ contract ZoraCreator1155FactoryForkTest is ForkDeploymentConfig, Test {
testTheFork(forkTestChains[i]);
}
}

// this is a temporary test to simulate the upgrade to the correct factory implementation
// on zora goerli. it can be deleted post upgrade
function test_fork_zoraGoerli_factoryUpgradeCanMint() external {
// create and select the fork, which will be used for all subsequent calls
// it will also affect the current block chain id based on the rpc url returned
vm.createSelectFork(vm.rpcUrl("zora_goerli"));

Deployment memory deployment = getDeployment();

address factoryAddress = deployment.factoryProxy;
ZoraCreator1155FactoryImpl factory = ZoraCreator1155FactoryImpl(factoryAddress);

vm.prank(factory.owner());

factory.upgradeTo(deployment.factoryImpl);

// sanity check - check minters match config
assertEq(address(factory.merkleMinter()), deployment.merkleMintSaleStrategy);
assertEq(address(factory.fixedPriceMinter()), deployment.fixedPriceSaleStrategy);
assertEq(address(factory.redeemMinterFactory()), deployment.redeemMinterFactory);

mintTokenAtFork(factory);
}
}
133 changes: 133 additions & 0 deletions packages/1155-contracts/test/nft/ZoraCreator1155.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ contract ZoraCreator1155Test is Test {
address internal zora;

event Purchased(address indexed sender, address indexed minter, uint256 indexed tokenId, uint256 quantity, uint256 value);
event RewardsDeposit(
address indexed creator,
address indexed createReferral,
address indexed mintReferral,
address firstMinter,
address zora,
address from,
uint256 creatorReward,
uint256 createReferralReward,
uint256 mintReferralReward,
uint256 firstMinterReward,
uint256 zoraReward
);

function setUp() external {
creator = makeAddr("creator");
Expand Down Expand Up @@ -1135,6 +1148,126 @@ contract ZoraCreator1155Test is Test {
assertEq(protocolRewards.balanceOf(zora), settings.zoraReward + settings.createReferralReward);
}

function test_SetCreatorRewardRecipientForToken() public {
address collaborator = makeAddr("collaborator");
uint256 quantity = 100;

init();

vm.prank(admin);
uint256 tokenId = target.setupNewToken("test", quantity);

address creatorRewardRecipient;

creatorRewardRecipient = target.getCreatorRewardRecipient(tokenId);

ICreatorRoyaltiesControl.RoyaltyConfiguration memory newRoyaltyConfig = ICreatorRoyaltiesControl.RoyaltyConfiguration(0, 0, collaborator);

vm.prank(admin);
target.updateRoyaltiesForToken(tokenId, newRoyaltyConfig);

creatorRewardRecipient = target.getCreatorRewardRecipient(tokenId);

assertEq(creatorRewardRecipient, collaborator);

vm.prank(admin);
target.addPermission(tokenId, address(simpleMinter), adminRole);

RewardsSettings memory settings = target.computeFreeMintRewards(quantity);

uint256 totalReward = target.computeTotalReward(quantity);
vm.deal(collector, totalReward);

vm.prank(collector);
vm.expectEmit(true, true, true, true);
emit RewardsDeposit(
collaborator,
zora,
zora,
collaborator,
zora,
address(target),
settings.creatorReward,
settings.createReferralReward,
settings.mintReferralReward,
settings.firstMinterReward,
settings.zoraReward
);
target.mintWithRewards{value: totalReward}(simpleMinter, tokenId, quantity, abi.encode(recipient), address(0));

assertEq(protocolRewards.balanceOf(collaborator), settings.creatorReward + settings.firstMinterReward);
}

function test_CreatorRewardRecipientConditionalAddress() public {
ICreatorRoyaltiesControl.RoyaltyConfiguration memory royaltyConfig;
address creatorRewardRecipient;

address collaborator = makeAddr("collaborator");
uint256 quantity = 100;

init();

vm.prank(admin);
uint256 tokenId = target.setupNewToken("test", quantity);

(, , address contractFundsRecipient, , , ) = target.config();

creatorRewardRecipient = target.getCreatorRewardRecipient(tokenId);
assertEq(creatorRewardRecipient, contractFundsRecipient);

royaltyConfig = ICreatorRoyaltiesControl.RoyaltyConfiguration(0, 0, collaborator);
vm.prank(admin);
target.updateRoyaltiesForToken(tokenId, royaltyConfig);

creatorRewardRecipient = target.getCreatorRewardRecipient(tokenId);
assertEq(creatorRewardRecipient, collaborator);

royaltyConfig = ICreatorRoyaltiesControl.RoyaltyConfiguration(0, 0, address(0));
vm.prank(admin);
target.updateRoyaltiesForToken(tokenId, royaltyConfig);

vm.prank(admin);
target.setFundsRecipient(payable(address(0)));

creatorRewardRecipient = target.getCreatorRewardRecipient(tokenId);
assertEq(creatorRewardRecipient, address(target));
}

function test_ContractAsCreatorRewardRecipientFallback() public {
uint256 quantity = 100;

init();

vm.startPrank(admin);
uint256 tokenId = target.setupNewToken("test", quantity);

target.setFundsRecipient(payable(address(0)));

target.addPermission(tokenId, address(simpleMinter), adminRole);
vm.stopPrank();

RewardsSettings memory settings = target.computeFreeMintRewards(quantity);

uint256 totalReward = target.computeTotalReward(quantity);
vm.deal(collector, totalReward);

address creatorRewardRecipient = target.getCreatorRewardRecipient(tokenId);

vm.prank(collector);
target.mintWithRewards{value: totalReward}(simpleMinter, tokenId, quantity, abi.encode(recipient), address(0));

assertEq(creatorRewardRecipient, address(target));

uint256 creatorRewardBalance = settings.creatorReward + settings.firstMinterReward;
assertEq(protocolRewards.balanceOf(address(target)), creatorRewardBalance);

vm.prank(admin);
target.withdrawRewards(admin, creatorRewardBalance);

assertEq(admin.balance, creatorRewardBalance);
assertEq(protocolRewards.balanceOf(address(target)), 0);
}

function testRevert_WrongValueForSale(uint256 quantity, uint256 salePrice) public {
vm.assume(quantity > 0 && quantity < 1_000_000);
vm.assume(salePrice > 0 && salePrice < 10 ether);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,40 +292,6 @@ contract ZoraCreator1155PreminterTest is ForkDeploymentConfig, Test {
}
}

// this is a temporary test to simulate the upcoming upgrade
function test_fork_zoraGoerli_afterUpgradeCanPremint() external {
vm.createSelectFork(vm.rpcUrl("zora_goerli"));

Deployment memory deployment = getDeployment();

factory = ZoraCreator1155FactoryImpl(deployment.factoryProxy);

console2.log("factory upgrade target:", deployment.factoryProxy);
bytes memory factoryProxyUpgradeCall = abi.encodeWithSelector(UUPSUpgradeable.upgradeTo.selector, deployment.factoryImpl);
console2.log("factory upgrade call:", vm.toString(factoryProxyUpgradeCall));

console2.log("preminter upgrade target:", deployment.preminterProxy);
bytes memory preminterProxyUpgradeCall = abi.encodeWithSelector(UUPSUpgradeable.upgradeTo.selector, deployment.preminterImpl);
console2.log("preminter upgrade call:", vm.toString(preminterProxyUpgradeCall));

vm.prank(factory.owner());
// lets call it as if we were calling from a safe:
deployment.factoryProxy.call(factoryProxyUpgradeCall);

// override test storage to point to proxy
preminter = ZoraCreator1155PremintExecutorImpl(deployment.preminterProxy);

vm.prank(preminter.owner());
// preminter impl was already created with correct factory, were just upgrading it now
deployment.preminterProxy.call(preminterProxyUpgradeCall);

assertEq(address(preminter.zora1155Factory()), address(factory));

preminterCanMintTokens();

// lets console.log these upgrades
}

function test_signatureForSameContractandUid_shouldMintExistingToken() external {
// 1. Make contract creation params

Expand Down

0 comments on commit f00bd85

Please sign in to comment.