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

Fix Transfer Hook 1155 Single Token Transfer #205

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
fs_permissions = [{access = "read", path = "./addresses"}, {access = "read", path = "./chainConfigs"}, {access = "read", path = "./package.json"}]
libs = ['_imagine', 'node_modules', 'script']
optimizer = true
optimizer_runs = 250
optimizer_runs = 150
out = 'out'
solc_version = '0.8.17'
src = 'src'
via_ir = true

[profile.optimized]
optimizer = true
optimizer_runs = 250
optimizer_runs = 150
out = 'out'
script = 'src'
solc_version = '0.8.17'
Expand All @@ -19,7 +19,7 @@ test = 'src'
via_ir = true

[profile.fast_compilation]
optimizer_runs = 200
optimizer_runs = 150
solc_version = '0.8.17'

[rpc_endpoints]
Expand Down
17 changes: 17 additions & 0 deletions src/interfaces/ITransferHookReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,22 @@ interface ITransferHookReceiver is IERC165Upgradeable {
bytes memory data
) external;

/// @notice Token transfer batch callback
/// @param target target contract for transfer
/// @param operator operator address for transfer
/// @param from user address for amount transferred
/// @param to user address for amount transferred
/// @param id token id transferred
/// @param amount value transferred
/// @param data data as perscribed by 1155 standard
function onTokenTransfer(
address target,
address operator,
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) external;
// IERC165 type required
}
21 changes: 21 additions & 0 deletions src/nft/ZoraCreator1155Impl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,27 @@ contract ZoraCreator1155Impl is
emit ConfigUpdated(msg.sender, ConfigUpdate.TRANSFER_HOOK, config);
}

/// @notice Hook before token transfer that checks for a transfer hook integration
/// @param operator operator moving the tokens
/// @param from from address
/// @param to to address
/// @param id token id to move
/// @param amount amount of token
/// @param data data of token
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal override {
super._beforeTokenTransfer(operator, from, to, id, amount, data);
if (address(config.transferHook) != address(0)) {
config.transferHook.onTokenTransfer(address(this), operator, from, to, id, amount, data);
}
}

/// @notice Hook before token transfer that checks for a transfer hook integration
/// @param operator operator moving the tokens
/// @param from from address
Expand Down
98 changes: 98 additions & 0 deletions test/nft/ZoraCreator1155.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {ProtocolRewards} from "@zoralabs/protocol-rewards/src/ProtocolRewards.so
import {RewardsSettings} from "@zoralabs/protocol-rewards/src/abstract/RewardSplits.sol";
import {MathUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/utils/math/MathUpgradeable.sol";
import {ZoraCreator1155Impl} from "../../src/nft/ZoraCreator1155Impl.sol";
import {ITransferHookReceiver} from "../../src/interfaces/ITransferHookReceiver.sol";
import {Zora1155} from "../../src/proxies/Zora1155.sol";
import {ZoraCreatorFixedPriceSaleStrategy} from "../../src/minters/fixed-price/ZoraCreatorFixedPriceSaleStrategy.sol";

Expand All @@ -22,6 +23,39 @@ import {SimpleMinter} from "../mock/SimpleMinter.sol";
import {SimpleRenderer} from "../mock/SimpleRenderer.sol";
import {MockUpgradeGate} from "../mock/MockUpgradeGate.sol";


contract MockTransferHookReceiver is ITransferHookReceiver {
mapping(uint256 => bool) public hasTransfer;
function onTokenTransferBatch(
address target,
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) external {
for (uint256 i = 0; i < ids.length; i++) {
hasTransfer[ids[i]] = true;
}
}
function onTokenTransfer(
address target,
address operator,
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) external {
hasTransfer[id] = true;
}
function supportsInterface(bytes4 testInterface) external override view returns (bool) {
return testInterface == type(ITransferHookReceiver).interfaceId;
}
}


contract ZoraCreator1155Test is Test {
using stdJson for string;

Expand Down Expand Up @@ -467,6 +501,70 @@ contract ZoraCreator1155Test is Test {
assertEq(target.balanceOf(recipient, tokenId2), quantity2);
}


function test_adminMintBatchWithHook(uint256 quantity1, uint256 quantity2) external {
vm.assume(quantity1 < 1000);
vm.assume(quantity2 < 1000);
init();

vm.prank(admin);
uint256 tokenId1 = target.setupNewToken("test", 1000);

vm.prank(admin);
uint256 tokenId2 = target.setupNewToken("test", 1000);

uint256[] memory tokenIds = new uint256[](2);
uint256[] memory quantities = new uint256[](2);
tokenIds[0] = tokenId1;
tokenIds[1] = tokenId2;
quantities[0] = quantity1;
quantities[1] = quantity2;

MockTransferHookReceiver testHook = new MockTransferHookReceiver();

vm.prank(admin);
target.setTransferHook(testHook);

vm.prank(admin);
target.adminMintBatch(recipient, tokenIds, quantities, "");

IZoraCreator1155TypesV1.TokenData memory tokenData1 = target.getTokenInfo(tokenId1);
IZoraCreator1155TypesV1.TokenData memory tokenData2 = target.getTokenInfo(tokenId2);

assertEq(testHook.hasTransfer(tokenId1), true);
assertEq(testHook.hasTransfer(tokenId2), true);
assertEq(testHook.hasTransfer(1000), false);

assertEq(tokenData1.totalMinted, quantity1);
assertEq(tokenData2.totalMinted, quantity2);
assertEq(target.balanceOf(recipient, tokenId1), quantity1);
assertEq(target.balanceOf(recipient, tokenId2), quantity2);
}

function test_adminMintWithHook(uint256 quantity1) external {
vm.assume(quantity1 < 1000);
init();

vm.prank(admin);
uint256 tokenId1 = target.setupNewToken("test", 1000);

MockTransferHookReceiver testHook = new MockTransferHookReceiver();

vm.prank(admin);
target.setTransferHook(testHook);

vm.prank(admin);
target.adminMint(recipient, tokenId1, quantity1, "");

IZoraCreator1155TypesV1.TokenData memory tokenData1 = target.getTokenInfo(tokenId1);

assertEq(testHook.hasTransfer(tokenId1), true);
assertEq(testHook.hasTransfer(1000), false);

assertEq(tokenData1.totalMinted, quantity1);
assertEq(target.balanceOf(recipient, tokenId1), quantity1);
}

function test_adminMintBatchWithSchedule(uint256 quantity1, uint256 quantity2) external {
vm.assume(quantity1 < 900);
vm.assume(quantity2 < 900);
Expand Down
Loading