Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: creator reward recipient per token #278

Merged
merged 14 commits into from
Oct 25, 2023
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
---

Add support for multiple creator reward recipients on a shared contract
6 changes: 3 additions & 3 deletions packages/1155-contracts/addresses/999.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"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",
"FACTORY_IMPL": "0x15ba66e376856F3F6FE53dE9eeAb10dEF10E8C92",
"FACTORY_PROXY": "0x777777C338d93e2C7adf08D102d45CA7CC4Ed021",
"FIXED_PRICE_SALE_STRATEGY": "0x04E2516A2c207E84a1839755675dfd8eF6302F0a",
"MERKLE_MINT_SALE_STRATEGY": "0xf48172CA3B6068B20eE4917Eb27b5472f1f272C7",
Expand Down
23 changes: 18 additions & 5 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 @@ -454,9 +454,22 @@ contract ZoraCreator1155Impl is
}

/// @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));
/// @param tokenId The token id to get the creator reward recipient for
/// @dev The creator is not enforced to set a funds recipient address for a specific token or contract-wide,
/// so in the case of both the reward recipient is set to the creator's contract,
/// which can be withdrawn by the creator 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));
kulkarohan marked this conversation as resolved.
Show resolved Hide resolved

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
Loading