diff --git a/contracts/interfaces/modules/collect/ICollectModule.sol b/contracts/interfaces/modules/collect/ICollectModule.sol index 803cdc45..cb9afbd5 100644 --- a/contracts/interfaces/modules/collect/ICollectModule.sol +++ b/contracts/interfaces/modules/collect/ICollectModule.sol @@ -7,6 +7,26 @@ import { Collect } from "contracts/lib/modules/Collect.sol"; /// @notice The collect module enables IP assets to be minted as NFTs mirroring /// their binding IP assets in a franchise-configurable format. interface ICollectModule { + + /// @dev Emits when a Collect action is invoked. + /// TODO: Once global IPs are supported, we can index the collect NFTs as well. + event Collected( + uint256 indexed franchiseid_, + uint256 indexed ipAssetId_, + address indexed collector_, + address collectNft_, + uint256 collectNftId_, + bytes collectData_, + bytes collectNftData_ + ); + + /// @dev Emits when a new collect NFT is deployed. + event NewCollectNFT( + uint256 indexed franchiseId_, + uint256 indexed ipAssetId_, + address collectNFT_ + ); + /// @notice Initializes the collect module for a specific IP asset. /// @param initCollectParams_ Collect module init data, including IP asset /// id, collect NFT impl address, and generic unformatted init data. diff --git a/contracts/modules/collect/CollectModuleBase.sol b/contracts/modules/collect/CollectModuleBase.sol index 983bf2da..3054e379 100644 --- a/contracts/modules/collect/CollectModuleBase.sol +++ b/contracts/modules/collect/CollectModuleBase.sol @@ -127,6 +127,17 @@ abstract contract CollectModuleBase is AccessControlledUpgradeable, ICollectModu // Perform any additional collect module processing. _collect(collectParams_); + // Emit the Collect event. + emit Collected( + franchiseId, + ipAssetId, + collectParams_.collector, + collectNft, + collectNftId, + collectParams_.collectData, + collectParams_.collectNftData + ); + return (collectNft, collectNftId); } @@ -174,6 +185,9 @@ abstract contract CollectModuleBase is AccessControlledUpgradeable, ICollectModu data: initData_ })); $.collectInfo[franchiseId_][ipAssetId_].collectNft = collectNft; + + // Emit the event indicating a new Collect NFT was created. + emit NewCollectNFT(franchiseId_, ipAssetId_, collectNft); } return collectNft; } diff --git a/test/foundry/modules/collect/BaseCollectModuleTest.sol b/test/foundry/modules/collect/BaseCollectModuleTest.sol index cf01712b..9e68af98 100644 --- a/test/foundry/modules/collect/BaseCollectModuleTest.sol +++ b/test/foundry/modules/collect/BaseCollectModuleTest.sol @@ -18,6 +18,25 @@ import { Errors } from "contracts/lib/Errors.sol"; /// @notice Provides a set of reusable tests for ERC-721 implementations. contract BaseCollectModuleTest is BaseTest { + // TODO: Currently, when compiling with 0.8.21, there is a known ICE bug that prevents us from emitting from the interface directly e.g. via ICollectModule.Collected - these two should be refactored in favor of emitting through the interface once we officially migrate to 0.8.22. + // See: https://github.com/ethereum/solidity/issues/14430 + event Collected( + uint256 indexed franchiseid_, + uint256 indexed ipAssetId_, + address indexed collector_, + address collectNft_, + uint256 collectNftId_, + bytes collectData_, + bytes collectNftData_ + ); + + // TODO: Refactor once we migrate to compiling via 0.8.22 as explained above. + event NewCollectNFT( + uint256 indexed franchiseId_, + uint256 indexed ipAssetId_, + address collectNFT_ + ); + // In the base collect module, an IP asset configured with a zero address // collect NFT impl means that the module-wide default should be used. address public constant DEFAULT_COLLECT_NFT_IMPL_CONFIG = address(0); @@ -73,6 +92,22 @@ contract BaseCollectModuleTest is BaseTest { /// @notice Tests that collects with the module-default collect NFT succeed. function test_CollectModuleCollectDefaultCollectNFT(uint8 ipAssetType) createIpAsset(collector, ipAssetType) public { assertEq(collectModule.getCollectNFT(franchiseId, ipAssetId), address(0)); + vm.expectEmit(true, true, false, false, address(collectModule)); + emit NewCollectNFT( + franchiseId, + ipAssetId, + defaultCollectNftImpl + ); + vm.expectEmit(true, true, true, false, address(collectModule)); + emit Collected( + franchiseId, + ipAssetId, + collector, + defaultCollectNftImpl, + 0, + "", + "" + ); (address collectNft, uint256 collectNftId) = _collect(franchiseId, ipAssetId); assertEq(collectModule.getCollectNFT(franchiseId, ipAssetId), collectNft); assertTrue(ICollectNFT(collectNft).ownerOf(collectNftId) == cal); @@ -82,6 +117,22 @@ contract BaseCollectModuleTest is BaseTest { /// @notice Tests that collects with customized collect NFTs succeed. function test_CollectModuleCollectCustomCollectNFT(uint8 ipAssetType) public createIpAsset(collector, ipAssetType) { assertEq(collectModule.getCollectNFT(franchiseId, ipAssetId), address(0)); + vm.expectEmit(true, true, false, false, address(collectModule)); + emit NewCollectNFT( + franchiseId, + ipAssetId, + defaultCollectNftImpl + ); + vm.expectEmit(true, true, true, false, address(collectModule)); + emit Collected( + franchiseId, + ipAssetId, + collector, + defaultCollectNftImpl, + 0, + "", + "" + ); (address collectNft, uint256 collectNftId) = _collect(franchiseId, ipAssetId); assertEq(collectModule.getCollectNFT(franchiseId, ipAssetId), collectNft); assertTrue(ICollectNFT(collectNft).ownerOf(collectNftId) == cal);