From 0318206911eeb6661ffd3c06d99d956c74585ffb Mon Sep 17 00:00:00 2001 From: Rob Green Date: Mon, 2 Oct 2023 08:16:36 -0700 Subject: [PATCH 01/63] Emit Register event (#23) --- src/PatchworkNFTBase.sol | 1 + src/PatchworkNFTInterface.sol | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/PatchworkNFTBase.sol b/src/PatchworkNFTBase.sol index 32fd01a..8a31ddd 100644 --- a/src/PatchworkNFTBase.sol +++ b/src/PatchworkNFTBase.sol @@ -517,6 +517,7 @@ abstract contract PatchworkLiteRef is IPatchworkLiteRef, ERC165 { } _referenceAddresses[refId] = addr; _referenceAddressIds[addr] = refId; + emit Register(address(this), addr, refId); return refId; } diff --git a/src/PatchworkNFTInterface.sol b/src/PatchworkNFTInterface.sol index d95ef1d..504dde4 100644 --- a/src/PatchworkNFTInterface.sol +++ b/src/PatchworkNFTInterface.sol @@ -295,6 +295,14 @@ interface IPatchworkLiteRef { */ event Unredact(address indexed target, address indexed fragment); + /** + @notice Emitted when a contract registers a fragment + @param target the contract that registered the fragment + @param fragment the fragment that was registered + @param idx the idx of the literef + */ + event Register(address indexed target, address indexed fragment, uint8 idx); + /** @notice Registers a reference address @param addr Address to register From edc3827f60910562c029bb8a637b333fa5caea36 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Mon, 2 Oct 2023 11:31:40 -0700 Subject: [PATCH 02/63] whitlisting enabled by default (#22) --- src/PatchworkProtocol.sol | 2 +- test/PatchworkNFTBase.t.sol | 2 ++ test/PatchworkProtocol.t.sol | 12 +++++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 7933228..ca2483f 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -344,7 +344,7 @@ contract PatchworkProtocol { revert ScopeExists(scopeName); } s.owner = msg.sender; - // s.requireWhitelist = true; // better security by default - enable in future PR + s.requireWhitelist = true; // better security by default emit ScopeClaim(scopeName, msg.sender); } diff --git a/test/PatchworkNFTBase.t.sol b/test/PatchworkNFTBase.t.sol index b28fef1..d04098b 100644 --- a/test/PatchworkNFTBase.t.sol +++ b/test/PatchworkNFTBase.t.sol @@ -38,6 +38,8 @@ contract PatchworkNFTBaseTest is Test { scopeName = "testscope"; vm.prank(scopeOwner); prot.claimScope(scopeName); + vm.prank(scopeOwner); + prot.setScopeRules(scopeName, false, false, false); vm.prank(userAddress); testBaseNFT = new TestBaseNFT(); diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index e9201d6..b68057b 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -84,6 +84,7 @@ contract PatchworkProtocolTest is Test { function testCreatePatchNFTNoVerification() public { vm.startPrank(scopeOwner); prot.claimScope(scopeName); + prot.setScopeRules(scopeName, false, false, false); uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); uint256 tokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); assertEq(tokenId, 0); @@ -92,7 +93,6 @@ contract PatchworkProtocolTest is Test { function testCreatePatchNFTUnverified() public { vm.startPrank(scopeOwner); prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, true); uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotWhitelisted.selector, scopeName, address(testPatchLiteRefNFT))); prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); @@ -101,7 +101,6 @@ contract PatchworkProtocolTest is Test { function testCreatePatchNFTVerified() public { vm.startPrank(scopeOwner); prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, true); vm.stopPrank(); vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); @@ -124,7 +123,6 @@ contract PatchworkProtocolTest is Test { function testUserPermissions() public { vm.startPrank(scopeOwner); prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, true); uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); prot.addWhitelist(scopeName, address(testPatchLiteRefNFT)); prot.addWhitelist(scopeName, address(testFragmentLiteRefNFT)); @@ -164,6 +162,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(scopeOwner); prot.claimScope(scopeName); + prot.setScopeRules(scopeName, false, false, false); uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.AlreadyPatched.selector, address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT))); patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); @@ -287,6 +286,7 @@ contract PatchworkProtocolTest is Test { uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); vm.startPrank(scopeOwner); prot.claimScope(scopeName); + prot.setScopeRules(scopeName, false, false, false); uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); uint256 fragmentTokenId = testFragmentLiteRefNFT.mint(user2Address); @@ -307,6 +307,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(scopeOwner); prot.claimScope(scopeName); + prot.setScopeRules(scopeName, false, false, false); uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); @@ -369,6 +370,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(scopeOwner); prot.claimScope(scopeName); + prot.setScopeRules(scopeName, false, false, false); uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); address[] memory fragmentAddresses = new address[](8); uint256[] memory fragments = new uint256[](8); @@ -528,6 +530,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(scopeOwner); prot.claimScope(scopeName); + prot.setScopeRules(scopeName, false, false, false); //Register testFragmentLiteRefNFT to testFragmentLiteRefNFT to allow recursion testFragmentLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); @@ -557,6 +560,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(scopeOwner); prot.claimScope(scopeName); + prot.setScopeRules(scopeName, false, false, false); //Register testFragmentLiteRefNFT to testFragmentLiteRefNFT to allow recursion testFragmentLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); @@ -596,6 +600,7 @@ contract PatchworkProtocolTest is Test { testPatchworkNFT.mint(userAddress, 1); vm.startPrank(scopeOwner); prot.claimScope(scopeName); + prot.setScopeRules(scopeName, false, false, false); uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); @@ -660,6 +665,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(scopeOwner); prot.claimScope(scopeName); + prot.setScopeRules(scopeName, false, false, false); uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); From 5820e9ab0fc4238ac2782b49e420d4be7b2ac18c Mon Sep 17 00:00:00 2001 From: Rob Green Date: Mon, 2 Oct 2023 11:32:04 -0700 Subject: [PATCH 03/63] Add schema update event (#24) --- src/PatchworkNFTInterface.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/PatchworkNFTInterface.sol b/src/PatchworkNFTInterface.sol index 504dde4..d19ef31 100644 --- a/src/PatchworkNFTInterface.sol +++ b/src/PatchworkNFTInterface.sol @@ -95,6 +95,12 @@ interface IPatchworkNFT is PatchworkNFTInterfaceMeta, IERC5192 { */ event PermissionChange(address indexed to, uint256 permissions); + /** + @notice Emitted when the schema has changed for an NFT + @param addr the address of the NFT + */ + event SchemaChange(address indexed addr); + /** @notice Get the scope this NFT claims to belong to @return string the name of the scope From 3c49f1892602fd9c6c664ea081246c80b7b1fe60 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Tue, 3 Oct 2023 10:56:30 -0700 Subject: [PATCH 04/63] Added getters for registered fragment data (#27) --- src/PatchworkNFTBase.sol | 15 +++++++++++++++ src/PatchworkNFTInterface.sol | 16 ++++++++++++++++ test/PatchworkNFTBase.t.sol | 6 ++++++ 3 files changed, 37 insertions(+) diff --git a/src/PatchworkNFTBase.sol b/src/PatchworkNFTBase.sol index 8a31ddd..d7c145c 100644 --- a/src/PatchworkNFTBase.sol +++ b/src/PatchworkNFTBase.sol @@ -521,6 +521,21 @@ abstract contract PatchworkLiteRef is IPatchworkLiteRef, ERC165 { return refId; } + /** + @dev See {IPatchworkLiteRef-getReferenceId} + */ + function getReferenceId(address addr) public virtual returns (uint8 id, bool redacted) { + uint8 refId = _referenceAddressIds[addr]; + return (refId, _redactedReferenceIds[refId]); + } + + /** + @dev See {IPatchworkLiteRef-getReferenceAddress} + */ + function getReferenceAddress(uint8 id) public virtual returns (address addr, bool redacted) { + return (_referenceAddresses[id], _redactedReferenceIds[id]); + } + /** @dev See {IPatchworkLiteRef-redactReferenceAddress} */ diff --git a/src/PatchworkNFTInterface.sol b/src/PatchworkNFTInterface.sol index d19ef31..5fd1d7c 100644 --- a/src/PatchworkNFTInterface.sol +++ b/src/PatchworkNFTInterface.sol @@ -316,6 +316,22 @@ interface IPatchworkLiteRef { */ function registerReferenceAddress(address addr) external returns (uint8 id); + /** + @notice Gets the ID assigned to the address from registration + @param addr Registered address + @return id ID assigned to the address + @return redacted Redacted status + */ + function getReferenceId(address addr) external returns (uint8 id, bool redacted); + + /** + @notice Gets the address assigned to this id + @param id ID assigned to the address + @return addr Registered address + @return redacted Redacted status + */ + function getReferenceAddress(uint8 id) external returns (address addr, bool redacted); + /** @notice Redacts a reference address @param id ID of the address to redact diff --git a/test/PatchworkNFTBase.t.sol b/test/PatchworkNFTBase.t.sol index d04098b..c55571a 100644 --- a/test/PatchworkNFTBase.t.sol +++ b/test/PatchworkNFTBase.t.sol @@ -198,6 +198,12 @@ contract PatchworkNFTBaseTest is Test { assertEq(0, ref); vm.prank(scopeOwner); refIdx = testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + (uint8 _id, bool _redacted) = testPatchLiteRefNFT.getReferenceId(address(testFragmentLiteRefNFT)); + assertEq(refIdx, _id); + assertFalse(_redacted); + (address _addr, bool _redacted2) = testPatchLiteRefNFT.getReferenceAddress(refIdx); + assertEq(address(testFragmentLiteRefNFT), _addr); + assertFalse(_redacted2); (ref, redacted) = testPatchLiteRefNFT.getLiteReference(address(testFragmentLiteRefNFT), 1); (address refAddr, uint256 tokenId) = testPatchLiteRefNFT.getReferenceAddressAndTokenId(ref); assertEq(address(testFragmentLiteRefNFT), refAddr); From 6873a3624121848ad0b4558485299be4740be008 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Sat, 7 Oct 2023 18:27:34 -0700 Subject: [PATCH 05/63] Fixed literef collisions in scope (#25) --- src/PatchworkProtocol.sol | 43 +++++++++++++++++++----------------- test/PatchworkProtocol.t.sol | 20 +++++++++++++++++ 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index ca2483f..87b93fc 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -228,9 +228,9 @@ contract PatchworkProtocol { /** @notice Mapped list of lightweight references within this scope - // TODO: A unique hash of liteRefAddr + reference will be needed for uniqueness + @dev A hash of liteRefAddr + reference provides uniqueness */ - mapping(uint64 => bool) liteRefs; + mapping(bytes32 => bool) liteRefs; /** @notice Mapped whitelist of addresses that belong to this scope @@ -541,26 +541,28 @@ contract PatchworkProtocol { } else { revert NotAuthorized(msg.sender); } + bytes32 targetRef; // reduce stack to stay under limit - uint64 ref; - { - (uint64 _ref, bool redacted) = IPatchworkLiteRef(target).getLiteReference(fragment, fragmentTokenId); - ref = _ref; - if (ref == 0) { - revert FragmentUnregistered(address(fragment)); - } - if (redacted) { - revert FragmentRedacted(address(fragment)); - } - if (scope.liteRefs[ref]) { - revert FragmentAlreadyAssignedInScope(scopeName, address(fragment), fragmentTokenId); - } + address _target = target; + uint256 _targetTokenId = targetTokenId; + address _fragment = fragment; + uint256 _fragmentTokenId = fragmentTokenId; + (uint64 ref, bool redacted) = IPatchworkLiteRef(_target).getLiteReference(_fragment, _fragmentTokenId); + targetRef = keccak256(abi.encodePacked(_target, ref)); + if (ref == 0) { + revert FragmentUnregistered(address(_fragment)); + } + if (redacted) { + revert FragmentRedacted(address(_fragment)); + } + if (scope.liteRefs[targetRef]) { + revert FragmentAlreadyAssignedInScope(scopeName, address(_fragment), _fragmentTokenId); } // call assign on the fragment - assignableNFT.assign(fragmentTokenId, target, targetTokenId); + assignableNFT.assign(_fragmentTokenId, _target, _targetTokenId); // add to our storage of scope->target assignments - scope.liteRefs[ref] = true; - emit Assign(targetOwner, fragment, fragmentTokenId, target, targetTokenId); + scope.liteRefs[targetRef] = true; + emit Assign(targetOwner, _fragment, _fragmentTokenId, _target, _targetTokenId); return ref; } @@ -593,10 +595,11 @@ contract PatchworkProtocol { if (ref == 0) { revert FragmentUnregistered(address(fragment)); } - if (!scope.liteRefs[ref]) { + bytes32 targetRef = keccak256(abi.encodePacked(target, ref)); + if (!scope.liteRefs[targetRef]) { revert RefNotFoundInScope(scopeName, target, fragment, fragmentTokenId); } - scope.liteRefs[ref] = false; + scope.liteRefs[targetRef] = false; IPatchworkLiteRef(target).removeReference(targetTokenId, ref); emit Unassign(IERC721(target).ownerOf(targetTokenId), fragment, fragmentTokenId, target, targetTokenId); } diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index b68057b..1cd9813 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -771,4 +771,24 @@ contract PatchworkProtocolTest is Test { vm.prank(userAddress); testFragmentLiteRefNFT.transferFrom(userAddress, user2Address, fragment1); } + + function testLiteRefCollision() public { + TestFragmentLiteRefNFT testFrag2 = new TestFragmentLiteRefNFT(address(prot)); + vm.startPrank(scopeOwner); + prot.claimScope(scopeName); + prot.setScopeRules(scopeName, false, false, false); + testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + testFragmentLiteRefNFT.registerReferenceAddress(address(testFrag2)); + uint256 frag1 = testFragmentLiteRefNFT.mint(userAddress); + uint256 frag2 = testFrag2.mint(userAddress); + uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); + uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + prot.assignNFT(address(testFragmentLiteRefNFT), frag1, address(testPatchLiteRefNFT), patchTokenId); + // The second assign succeeding combined with the assertion that they are equal ref values means there is no collision in the scope. + prot.assignNFT(address(testFrag2), frag2, address(testFragmentLiteRefNFT), frag1); + // LiteRef IDs should match because it is idx1 tokenID 0 for both (0x1. 0x0) + (uint64 lr1,) = testPatchLiteRefNFT.getLiteReference(address(testFragmentLiteRefNFT), frag1); + (uint64 lr2,) = testFragmentLiteRefNFT.getLiteReference(address(testFrag2), frag2); + assertEq(lr1, lr2); + } } \ No newline at end of file From 7b61b0847b03de45c5566e87ba344b672b87ef6d Mon Sep 17 00:00:00 2001 From: Rob Green Date: Sat, 7 Oct 2023 18:32:49 -0700 Subject: [PATCH 06/63] Two step transfer (#26) * Two step transfer system * Added events --- src/PatchworkProtocol.sol | 63 ++++++++++++++++++++++++++++++++++-- test/PatchworkProtocol.t.sol | 37 +++++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 87b93fc..e441382 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -202,6 +202,12 @@ contract PatchworkProtocol { */ address owner; + /** + @notice Owner-elect + @dev Used in two-step transfer process. If this is set, only this owner can accept the transfer + */ + address ownerElect; + /** @notice Indicates whether a user is allowed to patch within this scope @dev True if a user can patch, false otherwise. If false, only operators and the scope owner can perform patching. @@ -284,6 +290,22 @@ contract PatchworkProtocol { */ event ScopeClaim(string indexed scopeName, address indexed owner); + /** + @notice Emitted when a scope has elected a new owner to transfer to + @param scopeName The name of the transferred scope + @param from The owner of the scope + @param to The owner-elect of the scope + */ + event ScopeTransferElect(string indexed scopeName, address indexed from, address indexed to); + + /** + @notice Emitted when a scope transfer is canceled + @param scopeName The name of the transferred scope + @param from The owner of the scope + @param to The owner-elect of the scope + */ + event ScopeTransferCancel(string indexed scopeName, address indexed from, address indexed to); + /** @notice Emitted when a scope is transferred @param scopeName The name of the transferred scope @@ -350,6 +372,7 @@ contract PatchworkProtocol { /** @notice Transfer ownership of a scope + @dev must be accepted by transferee - see {acceptScopeTransfer} @param scopeName Name of the scope @param newOwner Address of the new owner */ @@ -359,8 +382,44 @@ contract PatchworkProtocol { if (newOwner == address(0)) { revert ScopeTransferNotAllowed(address(0)); } - s.owner = newOwner; - emit ScopeTransfer(scopeName, msg.sender, newOwner); + s.ownerElect = newOwner; + emit ScopeTransferElect(scopeName, s.owner, s.ownerElect); + } + + /** + @notice Cancel a pending scope transfer + @param scopeName Name of the scope + */ + function cancelScopeTransfer(string calldata scopeName) public { + Scope storage s = _mustHaveScope(scopeName); + _mustBeOwner(s); + emit ScopeTransferCancel(scopeName, s.owner, s.ownerElect); + s.ownerElect = address(0); + } + + /** + @notice Accept a scope transfer + @param scopeName Name of the scope + */ + function acceptScopeTransfer(string calldata scopeName) public { + Scope storage s = _mustHaveScope(scopeName); + if (s.ownerElect == msg.sender) { + address oldOwner = s.owner; + s.owner = msg.sender; + s.ownerElect = address(0); + emit ScopeTransfer(scopeName, oldOwner, msg.sender); + } else { + revert NotAuthorized(msg.sender); + } + } + + /** + @notice Get owner-elect of a scope + @param scopeName Name of the scope + @return ownerElect Address of the scope's owner-elect + */ + function getScopeOwnerElect(string calldata scopeName) public view returns (address ownerElect) { + return _scopes[scopeName].ownerElect; } /** diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index 1cd9813..c2fa978 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -55,27 +55,64 @@ contract PatchworkProtocolTest is Test { vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.ScopeExists.selector, scopeName)); prot.claimScope(scopeName); vm.stopPrank(); + // Current user is not scope owner so can't transfer it vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, defaultUser)); prot.transferScopeOwnership(scopeName, address(2)); + // Real owner can transfer it + vm.prank(scopeOwner); + prot.transferScopeOwnership(scopeName, address(3)); + // scopeOwner still owns it until it's accepted + assertEq(prot.getScopeOwner(scopeName), scopeOwner); + assertEq(prot.getScopeOwnerElect(scopeName), address(3)); + // test changing the pending transfer elect vm.prank(scopeOwner); prot.transferScopeOwnership(scopeName, address(2)); + assertEq(prot.getScopeOwnerElect(scopeName), address(2)); + // Non-owner may not cancel the transfer + vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, address(10))); + vm.prank(address(10)); + prot.cancelScopeTransfer(scopeName); + // Real owner can cancel the transfer + vm.prank(scopeOwner); + prot.cancelScopeTransfer(scopeName); + assertEq(prot.getScopeOwnerElect(scopeName), address(0)); + // Now retry the transfer + vm.prank(scopeOwner); + prot.transferScopeOwnership(scopeName, address(2)); + // User 10 is not elect and may not accept the transfer + vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, address(10))); + vm.prank(address(10)); + prot.acceptScopeTransfer(scopeName); + // Finally real elect accepts scope transfer + vm.prank(address(2)); + prot.acceptScopeTransfer(scopeName); assertEq(prot.getScopeOwner(scopeName), address(2)); + assertEq(prot.getScopeOwnerElect(scopeName), address(0)); + // Old owner may not transfer it vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, scopeOwner)); vm.prank(scopeOwner); prot.transferScopeOwnership(scopeName, address(2)); + // New owner may transfer it back to old owner vm.prank(address(2)); prot.transferScopeOwnership(scopeName, scopeOwner); + vm.prank(scopeOwner); + prot.acceptScopeTransfer(scopeName); assertEq(prot.getScopeOwner(scopeName), scopeOwner); + // Non-owner may not add operator vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, address(2))); vm.prank(address(2)); prot.addOperator(scopeName, address(2)); + // Real owner may add operator vm.prank(scopeOwner); prot.addOperator(scopeName, address(2)); + // Non-owner may not remove operator vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, address(2))); vm.prank(address(2)); prot.removeOperator(scopeName, address(2)); + // Real owner may remove operator vm.prank(scopeOwner); prot.removeOperator(scopeName, address(2)); + // Non-owner may not set scope rules vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, address(2))); vm.prank(address(2)); prot.setScopeRules(scopeName, true, true, true); From c1da47529cac55201de665a4c9960a8773b6a023 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Sat, 7 Oct 2023 21:59:10 -0700 Subject: [PATCH 07/63] Patch an address (#28) * WIP * Refactor - split into own files * wip * wip * wip * wip, still need some custom errors and coverage * Most coverage in * Still needs custom errors * Custom errors * Rename param on storePatch --- src/IPatchworkAccountPatch.sol | 29 +++++++++ src/PatchworkAccountPatch.sol | 54 ++++++++++++++++ src/PatchworkNFTBase.sol | 7 ++ src/PatchworkNFTInterface.sol | 1 - src/PatchworkProtocol.sol | 64 +++++++++++++++++- src/sampleNFTs/TestAccountPatchNFT.sol | 70 ++++++++++++++++++++ src/sampleNFTs/TestPatchLiteRefNFT.sol | 8 +++ test/PatchworkAccountPatch.t.sol | 89 ++++++++++++++++++++++++++ test/PatchworkNFTBase.t.sol | 8 +++ test/PatchworkProtocol.t.sol | 4 +- 10 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 src/IPatchworkAccountPatch.sol create mode 100644 src/PatchworkAccountPatch.sol create mode 100644 src/sampleNFTs/TestAccountPatchNFT.sol create mode 100644 test/PatchworkAccountPatch.t.sol diff --git a/src/IPatchworkAccountPatch.sol b/src/IPatchworkAccountPatch.sol new file mode 100644 index 0000000..e837448 --- /dev/null +++ b/src/IPatchworkAccountPatch.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/** +@title Patchwork Protocol Account Patch Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting Patchwork patch standard +*/ +interface IPatchworkAccountPatch { + /** + @notice Get the scope this NFT claims to belong to + @return string the name of the scope + */ + function getScopeName() external returns (string memory); + + /** + @notice Creates a new token for the owner, representing a patch + @param owner Address of the owner of the token + @param originalAccountAddress Address of the original account + @return tokenId ID of the newly minted token + */ + function mintPatch(address owner, address originalAccountAddress) external returns (uint256 tokenId); + + /** + @notice A deliberately incompatible function to block implementing both assignable and patch + @return bytes3 Always returns 0x00 + */ + function patchworkCompatible_() external pure returns (bytes3); +} \ No newline at end of file diff --git a/src/PatchworkAccountPatch.sol b/src/PatchworkAccountPatch.sol new file mode 100644 index 0000000..a8fd506 --- /dev/null +++ b/src/PatchworkAccountPatch.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "./IPatchworkAccountPatch.sol"; +import "./PatchworkNFTBase.sol"; +import "./PatchworkProtocol.sol"; + +/** +@title PatchworkAccountPatch +@dev Base implementation of IPatchworkAccountPatch +@dev It extends the functionalities of PatchworkNFT and implements the IPatchworkAccountPatch interface. +*/ +abstract contract PatchworkAccountPatch is PatchworkNFT, IPatchworkAccountPatch { + + /// @dev Mapping from token ID to the address of the NFT that this patch is applied to. + mapping(uint256 => address) internal _patchedAddresses; + + /** + @dev See {IERC165-supportsInterface} + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(IPatchworkAccountPatch).interfaceId || + super.supportsInterface(interfaceID); + } + + /** + @dev See {IPatchworkNFT-getScopeName} + */ + function getScopeName() public view virtual override(PatchworkNFT, IPatchworkAccountPatch) returns (string memory) { + return _scopeName; + } + + /** + @notice stores a patch + @param tokenId the tokenId of the patch + @param originalAccountAddress the account we are patching + */ + function _storePatch(uint256 tokenId, address originalAccountAddress) internal virtual { + // PatchworkProtocol handles uniqueness assertion + _patchedAddresses[tokenId] = originalAccountAddress; + } + + /** + @dev See {ERC721-_burn} + */ + function _burn(uint256 /*tokenId*/) internal virtual override { + revert PatchworkProtocol.UnsupportedOperation(); + } + + /** + @dev See {IPatchworkPatch-patchworkCompatible_} + */ + function patchworkCompatible_() external pure returns (bytes3) {} +} \ No newline at end of file diff --git a/src/PatchworkNFTBase.sol b/src/PatchworkNFTBase.sol index d7c145c..48175c3 100644 --- a/src/PatchworkNFTBase.sol +++ b/src/PatchworkNFTBase.sol @@ -329,6 +329,13 @@ abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { revert PatchworkProtocol.CannotLockSoulboundPatch(address(this)); } + /** + @dev See {ERC721-_burn} + */ + function _burn(uint256 /*tokenId*/) internal virtual override { + revert PatchworkProtocol.UnsupportedOperation(); + } + /** @dev See {IPatchworkPatch-patchworkCompatible_} */ diff --git a/src/PatchworkNFTInterface.sol b/src/PatchworkNFTInterface.sol index 5fd1d7c..156e188 100644 --- a/src/PatchworkNFTInterface.sol +++ b/src/PatchworkNFTInterface.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "forge-std/console.sol"; import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import "./IERC5192.sol"; diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index e441382..e2daff0 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "./PatchworkNFTInterface.sol"; +import "./IPatchworkAccountPatch.sol"; /** @title Patchwork Protocol @@ -56,6 +57,13 @@ contract PatchworkProtocol { */ error NotWhitelisted(string scopeName, address addr); + /** + @notice The address at the given address has already been patched + @param addr The address that was patched + @param patchAddress Address of the patch applied + */ + error AccountAlreadyPatched(address addr, address patchAddress); + /** @notice The token at the given address has already been patched @param addr Address of the token owner @@ -162,11 +170,11 @@ contract PatchworkProtocol { error SelfAssignmentNotAllowed(address addr, uint256 tokenId); /** - @notice Transfer of the soulbound token with the provided ID at the given address is not allowed + @notice Transfer of the token with the provided ID at the given address is not allowed @param addr Address of the token owner @param tokenId ID of the token */ - error SoulboundTransferNotAllowed(address addr, uint256 tokenId); + error TransferNotAllowed(address addr, uint256 tokenId); /** @notice Transfer of the token with the provided ID at the given address is blocked by an assignment @@ -175,6 +183,12 @@ contract PatchworkProtocol { */ error TransferBlockedByAssignment(address addr, uint256 tokenId); + /** + @notice A rule is blocking the mint to this owner address + @param addr Address of the token owner + */ + error MintNotAllowed(address addr); + /** @notice The token at the given address is not IPatchworkAssignable @param addr Address of the non-assignable token @@ -191,6 +205,11 @@ contract PatchworkProtocol { */ error DataIntegrityError(address addr, uint256 tokenId, address addr2, uint256 tokenId2); + /** + @notice The operation is not supported + */ + error UnsupportedOperation(); + /** @notice Represents a defined scope within the system @dev Contains details about the scope ownership, permissions, and mappings for references and assignments @@ -283,6 +302,15 @@ contract PatchworkProtocol { */ event Patch(address indexed owner, address originalAddress, uint256 originalTokenId, address indexed patchAddress, uint256 indexed patchTokenId); + /** + @notice Emitted when an account patch is minted + @param owner The owner of the patch + @param originalAddress The address of the original NFT's contract + @param patchAddress The address of the patch's contract + @param patchTokenId The tokenId of the patch + */ + event AccountPatch(address indexed owner, address originalAddress, address indexed patchAddress, uint256 indexed patchTokenId); + /** @notice Emitted when a new scope is claimed @param scopeName The name of the claimed scope @@ -527,6 +555,36 @@ contract PatchworkProtocol { return tokenId; } + /** + @notice Create a new account patch + @param originalAddress Address of the original account + @param patchAddress Address of the IPatchworkPatch to mint + @return tokenId Token ID of the newly created patch + */ + function createAccountPatch(address owner, address originalAddress, address patchAddress) public returns (uint256 tokenId) { + IPatchworkAccountPatch patch = IPatchworkAccountPatch(patchAddress); + string memory scopeName = patch.getScopeName(); + // mint a Patch that is soulbound to the originalNFT using the contract address at patchAddress which must support Patchwork metadata + Scope storage scope = _mustHaveScope(scopeName); + _mustBeWhitelisted(scopeName, scope, patchAddress); + if (scope.owner == msg.sender || scope.operators[msg.sender]) { + // continue + } else if (scope.allowUserPatch) { // This allows any user to patch any address + // continue + } else { + revert NotAuthorized(msg.sender); + } + // limit this to one unique patch (originalAddress+TokenID+patchAddress) + bytes32 _hash = keccak256(abi.encodePacked(originalAddress, patchAddress)); + if (scope.uniquePatches[_hash]) { + revert AccountAlreadyPatched(originalAddress, patchAddress); + } + scope.uniquePatches[_hash] = true; + tokenId = patch.mintPatch(owner, originalAddress); + emit AccountPatch(owner, originalAddress, patchAddress, tokenId); + return tokenId; + } + /** @notice Assigns an NFT relation to have an IPatchworkLiteRef form a LiteRef to a IPatchworkAssignableNFT @param fragment The IPatchworkAssignableNFT address to assign @@ -679,7 +737,7 @@ contract PatchworkProtocol { } } if (IERC165(nft).supportsInterface(type(IPatchworkPatch).interfaceId)) { - revert SoulboundTransferNotAllowed(nft, tokenId); + revert TransferNotAllowed(nft, tokenId); } if (IERC165(nft).supportsInterface(type(IPatchworkNFT).interfaceId)) { if (IPatchworkNFT(nft).locked(tokenId)) { diff --git a/src/sampleNFTs/TestAccountPatchNFT.sol b/src/sampleNFTs/TestAccountPatchNFT.sol new file mode 100644 index 0000000..57f42f2 --- /dev/null +++ b/src/sampleNFTs/TestAccountPatchNFT.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "../PatchworkAccountPatch.sol"; + +contract TestAccountPatchNFT is PatchworkAccountPatch { + + uint256 _nextTokenId = 0; + bool _sameOwnerModel; + + struct TestPatchworkNFTMetadata { + uint256 thing; + } + + constructor(address manager_, bool sameOwnerModel_) PatchworkNFT("testscope", "TestAccountPatchNFT", "TPLR", msg.sender, manager_) { + _sameOwnerModel = sameOwnerModel_; + } + + function schemaURI() pure external returns (string memory) { + return "https://mything/my-nft-metadata.json"; + } + + function imageURI(uint256 _tokenId) pure external returns (string memory) { + return string(abi.encodePacked("https://mything/nft-", _tokenId)); + } + + function schema() pure external returns (MetadataSchema memory) { + MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](1); + entries[0] = MetadataSchemaEntry(1, 0, FieldType.UINT256, 0, FieldVisibility.PUBLIC, 2, 0, "thing"); + return MetadataSchema(1, entries); + } + + function mintPatch(address to, address original) public returns (uint256) { + if (msg.sender != _manager) { + revert PatchworkProtocol.NotAuthorized(msg.sender); + } + if (_sameOwnerModel) { + if (to != original) { + revert PatchworkProtocol.MintNotAllowed(to); + } + } + uint256 tokenId = _nextTokenId; + _nextTokenId++; + _storePatch(tokenId, original); + _mint(to, tokenId); + _metadataStorage[tokenId] = new uint256[](1); + return tokenId; + } + + function burn(uint256 tokenId) public { + // test only + _burn(tokenId); + } + + function _beforeTokenTransfer( + address from, + address to, + uint256 firstTokenId, + uint256 /*batchSize*/ + ) internal override view{ + if (_sameOwnerModel) { + // allow burn only + if (from == address(0)) { + // mint allowed + } else if (to != address(0)) { + revert PatchworkProtocol.TransferNotAllowed(address(this), firstTokenId); + } + } + } +} \ No newline at end of file diff --git a/src/sampleNFTs/TestPatchLiteRefNFT.sol b/src/sampleNFTs/TestPatchLiteRefNFT.sol index c099e32..cd72d9c 100644 --- a/src/sampleNFTs/TestPatchLiteRefNFT.sol +++ b/src/sampleNFTs/TestPatchLiteRefNFT.sol @@ -174,6 +174,9 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { } function mintPatch(address originalNFTOwner, address originalNFTAddress, uint originalNFTTokenId) external returns (uint256 tokenId){ + if (msg.sender != _manager) { + revert(); + } // Just for testing tokenId = _nextTokenId; _nextTokenId++; @@ -235,4 +238,9 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { function _checkWriteAuth() internal override(PatchworkNFT, PatchworkLiteRef) view returns (bool allow) { return PatchworkNFT._checkWriteAuth(); } + + function burn(uint256 tokenId) public { + // test only + _burn(tokenId); + } } \ No newline at end of file diff --git a/test/PatchworkAccountPatch.t.sol b/test/PatchworkAccountPatch.t.sol new file mode 100644 index 0000000..f6b5ecf --- /dev/null +++ b/test/PatchworkAccountPatch.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "../src/PatchworkProtocol.sol"; +import "../src/sampleNFTs/TestAccountPatchNFT.sol"; + +contract PatchworkAccountPatchTest is Test { + + PatchworkProtocol _prot; + + string _scopeName; + address _defaultUser; + address _scopeOwner; + address _patchworkOwner; + address _userAddress; + address _user2Address; + + function setUp() public { + _defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; + _patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; + _userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; + _user2Address = address(550001); + _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; + + vm.prank(_patchworkOwner); + _prot = new PatchworkProtocol(); + + vm.startPrank(_scopeOwner); + _scopeName = "testscope"; + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + + vm.stopPrank(); + } + + function testAccountPatchNotSameOwner() public { + // Not same owner model, yes transferrable + vm.prank(_scopeOwner); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); + // User patching is off, not authorized + vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); + vm.prank(_scopeOwner); + uint256 tokenId = _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); + assertEq(_userAddress, testAccountPatchNFT.ownerOf(tokenId)); + // Duplicate should fail + vm.prank(_scopeOwner); + vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.AccountAlreadyPatched.selector, _user2Address, address(testAccountPatchNFT))); + _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); + // Test transfer + vm.prank(_userAddress); + testAccountPatchNFT.transferFrom(_userAddress, address(55), tokenId); + vm.prank(address(55)); + vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.UnsupportedOperation.selector)); + testAccountPatchNFT.burn(tokenId); + } + + function testAccountPatchSameOwner() public { + // Same owner model, not transferrable + vm.prank(_scopeOwner); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), true); + vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.MintNotAllowed.selector, _userAddress)); + vm.prank(_scopeOwner); + _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); + vm.prank(_scopeOwner); + uint256 tokenId = _prot.createAccountPatch(_userAddress, _userAddress, address(testAccountPatchNFT)); + vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.TransferNotAllowed.selector, address(testAccountPatchNFT), tokenId)); + vm.prank(_userAddress); + testAccountPatchNFT.transferFrom(_userAddress, address(55), tokenId); + } + + function testAccountPatchUserPatch() public { + vm.prank(_scopeOwner); + _prot.setScopeRules(_scopeName, true, false, false); + // Not same owner model, yes transferrable + vm.prank(_scopeOwner); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); + // User patching is on + _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); + } + + function testPatchworkCompatible() public { + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); + testAccountPatchNFT.patchworkCompatible_(); + } +} \ No newline at end of file diff --git a/test/PatchworkNFTBase.t.sol b/test/PatchworkNFTBase.t.sol index c55571a..df67177 100644 --- a/test/PatchworkNFTBase.t.sol +++ b/test/PatchworkNFTBase.t.sol @@ -269,6 +269,14 @@ contract PatchworkNFTBaseTest is Test { refIdx = testPatchLiteRefNFT.registerReferenceAddress(address(256)); } + function testBurn() public { + uint256 baseTokenId = testBaseNFT.mint(userAddress); + vm.prank(scopeOwner); + uint256 patchTokenId = prot.createPatch(address(testBaseNFT), baseTokenId, address(testPatchLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.UnsupportedOperation.selector)); + testPatchLiteRefNFT.burn(patchTokenId); + } + function testOnAssignedTransferError() public { vm.expectRevert(); testFragmentLiteRefNFT.onAssignedTransfer(address(0), address(1), 1); diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index c2fa978..54b37b5 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -236,7 +236,7 @@ contract PatchworkProtocolTest is Test { vm.stopPrank(); vm.prank(userAddress); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.SoulboundTransferNotAllowed.selector, address(testPatchLiteRefNFT), patchTokenId)); + vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.TransferNotAllowed.selector, address(testPatchLiteRefNFT), patchTokenId)); testPatchLiteRefNFT.transferFrom(userAddress, user2Address, patchTokenId); } @@ -397,7 +397,7 @@ contract PatchworkProtocolTest is Test { // try to transfer a patch directly - it should be blocked because it is soulbound assertEq(address(7), testPatchLiteRefNFT.ownerOf(patchTokenId)); // Report as soulbound vm.startPrank(userAddress); // Prank from underlying owner address - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.SoulboundTransferNotAllowed.selector, address(testPatchLiteRefNFT), patchTokenId)); + vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.TransferNotAllowed.selector, address(testPatchLiteRefNFT), patchTokenId)); testPatchLiteRefNFT.transferFrom(userAddress, address(7), patchTokenId); vm.stopPrank(); } From 809a2a1098391a7603e5593394df1f4e439e4e10 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Sat, 7 Oct 2023 22:04:00 -0700 Subject: [PATCH 08/63] Refactor - Split all interfaces and base implementations (#30) * phase 1 - IPatchworkProtocol * Prot on interface entirely now, base contracts updated * More splitting refactorings * All base implementations split --- src/IPatchworkAssignableNFT.sol | 64 +++ src/IPatchworkLiteRef.sol | 120 +++++ src/IPatchworkNFT.sol | 179 +++++++ src/IPatchworkPatch.sol | 43 ++ src/IPatchworkProtocol.sol | 513 +++++++++++++++++++ src/PatchworkAccountPatch.sol | 6 +- src/PatchworkFragment.sol | 134 +++++ src/PatchworkLiteRef.sol | 126 +++++ src/PatchworkNFT.sol | 243 +++++++++ src/PatchworkNFTBase.sol | 592 ---------------------- src/PatchworkNFTInterface.sol | 399 --------------- src/PatchworkPatch.sol | 104 ++++ src/PatchworkProtocol.sol | 463 +---------------- src/sampleNFTs/TestAccountPatchNFT.sol | 6 +- src/sampleNFTs/TestFragmentLiteRefNFT.sol | 7 +- src/sampleNFTs/TestPatchLiteRefNFT.sol | 7 +- src/sampleNFTs/TestPatchworkNFT.sol | 3 +- test/PatchworkAccountPatch.t.sol | 10 +- test/PatchworkNFTBase.t.sol | 46 +- test/PatchworkNFTInterface.t.sol | 1 - test/PatchworkProtocol.t.sol | 124 ++--- 21 files changed, 1654 insertions(+), 1536 deletions(-) create mode 100644 src/IPatchworkAssignableNFT.sol create mode 100644 src/IPatchworkLiteRef.sol create mode 100644 src/IPatchworkNFT.sol create mode 100644 src/IPatchworkPatch.sol create mode 100644 src/IPatchworkProtocol.sol create mode 100644 src/PatchworkFragment.sol create mode 100644 src/PatchworkLiteRef.sol create mode 100644 src/PatchworkNFT.sol delete mode 100644 src/PatchworkNFTBase.sol delete mode 100644 src/PatchworkNFTInterface.sol create mode 100644 src/PatchworkPatch.sol diff --git a/src/IPatchworkAssignableNFT.sol b/src/IPatchworkAssignableNFT.sol new file mode 100644 index 0000000..903f67e --- /dev/null +++ b/src/IPatchworkAssignableNFT.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/** +@title Patchwork Protocol Assignable NFT Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting Patchwork assignment +*/ +interface IPatchworkAssignableNFT { + /** + @notice Get the scope this NFT claims to belong to + @return string the name of the scope + */ + function getScopeName() external returns (string memory); + + /** + @notice Assigns a token to another + @param ourTokenId ID of our token + @param to Address to assign to + @param tokenId ID of the token to assign + */ + function assign(uint256 ourTokenId, address to, uint256 tokenId) external; + + /** + @notice Unassigns a token + @param ourTokenId ID of our token + */ + function unassign(uint256 ourTokenId) external; + + /** + @notice Returns the address and token ID that our token is assigned to + @param ourTokenId ID of our token + @return address the address this is assigned to + @return uint256 the tokenId this is assigned to + */ + function getAssignedTo(uint256 ourTokenId) external view returns (address, uint256); + + /** + @notice Returns the underlying stored owner of a token ignoring current assignment + @param ourTokenId ID of our token + @return address address of the owner + */ + function unassignedOwnerOf(uint256 ourTokenId) external view returns (address); + + /** + @notice Sends events for a token when the assigned-to token has been transferred + @param from Sender address + @param to Recipient address + @param tokenId ID of the token + */ + function onAssignedTransfer(address from, address to, uint256 tokenId) external; + + /** + @notice Updates the real underlying ownership of a token in storage (if different from current) + @param tokenId ID of the token + */ + function updateOwnership(uint256 tokenId) external; + + /** + @notice A deliberately incompatible function to block implementing both assignable and patch + @return bytes2 Always returns 0x0000 + */ + function patchworkCompatible_() external pure returns (bytes2); +} \ No newline at end of file diff --git a/src/IPatchworkLiteRef.sol b/src/IPatchworkLiteRef.sol new file mode 100644 index 0000000..040f954 --- /dev/null +++ b/src/IPatchworkLiteRef.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/** +@title Patchwork Protocol LiteRef NFT Interface +@author Runic Labs, Inc +@notice Interface for contracts that have Lite Reference ID support +*/ +interface IPatchworkLiteRef { + /** + @notice Emitted when a contract redacts a fragment + @param target the contract which issued the redaction + @param fragment the fragment that was redacted + */ + event Redact(address indexed target, address indexed fragment); + + /** + @notice Emitted when a contract unredacts a fragment + @param target the contract which revoked the redaction + @param fragment the fragment that was unredacted + */ + event Unredact(address indexed target, address indexed fragment); + + /** + @notice Emitted when a contract registers a fragment + @param target the contract that registered the fragment + @param fragment the fragment that was registered + @param idx the idx of the literef + */ + event Register(address indexed target, address indexed fragment, uint8 idx); + + /** + @notice Registers a reference address + @param addr Address to register + @return id ID assigned to the address + */ + function registerReferenceAddress(address addr) external returns (uint8 id); + + /** + @notice Gets the ID assigned to the address from registration + @param addr Registered address + @return id ID assigned to the address + @return redacted Redacted status + */ + function getReferenceId(address addr) external returns (uint8 id, bool redacted); + + /** + @notice Gets the address assigned to this id + @param id ID assigned to the address + @return addr Registered address + @return redacted Redacted status + */ + function getReferenceAddress(uint8 id) external returns (address addr, bool redacted); + + /** + @notice Redacts a reference address + @param id ID of the address to redact + */ + function redactReferenceAddress(uint8 id) external; + + /** + @notice Unredacts a reference address + @param id ID of the address to unredact + */ + function unredactReferenceAddress(uint8 id) external; + + /** + @notice Returns a lite reference for a given address and token ID + @param addr Address to get reference for + @param tokenId ID of the token + @return liteRef Lite reference + @return redacted Redacted status + */ + function getLiteReference(address addr, uint256 tokenId) external view returns (uint64 liteRef, bool redacted); + + /** + @notice Returns an address and token ID for a given lite reference + @param liteRef Lite reference to get address and token ID for + @return addr Address + @return tokenId Token ID + */ + function getReferenceAddressAndTokenId(uint64 liteRef) external view returns (address addr, uint256 tokenId); + + /** + @notice Adds a reference to a token + @param tokenId ID of the token + @param referenceAddress Reference address to add + */ + function addReference(uint256 tokenId, uint64 referenceAddress) external; + + /** + @notice Adds multiple references to a token + @param tokenId ID of the token + @param liteRefs Array of lite references to add + */ + function batchAddReferences(uint256 tokenId, uint64[] calldata liteRefs) external; + + /** + @notice Removes a reference from a token + @param tokenId ID of the token + @param liteRef Lite reference to remove + */ + function removeReference(uint256 tokenId, uint64 liteRef) external; + + /** + @notice Loads a reference address and token ID at a given index + @param idx Index to load from + @return addr Address + @return tokenId Token ID + */ + function loadReferenceAddressAndTokenId(uint256 idx) external view returns (address addr, uint256 tokenId); + + /** + @notice Loads all references for a given token ID + @param tokenId ID of the token + @return addresses Array of addresses + @return tokenIds Array of token IDs + */ + function loadAllReferences(uint256 tokenId) external view returns (address[] memory addresses, uint256[] memory tokenIds); +} diff --git a/src/IPatchworkNFT.sol b/src/IPatchworkNFT.sol new file mode 100644 index 0000000..2e6416f --- /dev/null +++ b/src/IPatchworkNFT.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "./IERC5192.sol"; + +/** +@title Patchwork Protocol NFT Interface Metadata +@author Runic Labs, Inc +@notice Metadata for IPatchworkNFT and related contract interfaces +*/ +interface PatchworkNFTInterfaceMeta { + /** + @notice Enumeration of possible field data types. + @dev This defines the various basic data types for the fields. + */ + enum FieldType { + BOOLEAN, ///< A Boolean type (true or false). + INT8, ///< An 8-bit signed integer. + INT16, ///< A 16-bit signed integer. + INT32, ///< A 32-bit signed integer. + INT64, ///< A 64-bit signed integer. + INT128, ///< A 128-bit signed integer. + INT256, ///< A 256-bit signed integer. + UINT8, ///< An 8-bit unsigned integer. + UINT16, ///< A 16-bit unsigned integer. + UINT32, ///< A 32-bit unsigned integer. + UINT64, ///< A 64-bit unsigned integer. + UINT128, ///< A 128-bit unsigned integer. + UINT256, ///< A 256-bit unsigned integer. + CHAR8, ///< An 8-character string. + CHAR16, ///< A 16-character string. + CHAR32, ///< A 32-character string. + CHAR64, ///< A 64-character string. + LITEREF ///< A Literef reference to a patchwork fragment + } + + /** + @notice Struct defining the metadata schema. + @dev This defines the overall structure of the metadata and contains entries describing each data field. + */ + struct MetadataSchema { + uint256 version; ///< Version of the metadata schema. + MetadataSchemaEntry[] entries; ///< Array of entries in the schema. + } + + /** + @notice Struct defining individual entries within the metadata schema. + @dev Represents each data field in the schema, detailing its properties and type. + */ + struct MetadataSchemaEntry { + uint256 id; ///< Index or unique identifier of the entry. + uint256 permissionId; ///< Permission identifier associated with the entry. + FieldType fieldType; ///< Type of field data (from the FieldType enum). + uint256 arrayLength; ///< Length of array for the field (0 means it's a single field). + FieldVisibility visibility; ///< Visibility level of the field. + uint256 slot; ///< Starting storage slot, may span multiple slots based on width. + uint256 offset; ///< Offset in bits within the storage slot. + string key; ///< Key or name associated with the field. + } + + /** + @notice Enumeration of field visibility options. + @dev Specifies whether a field is publicly accessible or private. + */ + enum FieldVisibility { + PUBLIC, ///< Field is publicly accessible. + PRIVATE ///< Field is private + } +} + +// TODO - Protocol assumes this is IERC721. Should we just declare it here? +/** +@title Patchwork Protocol NFT Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting Patchwork metadata standard +*/ +interface IPatchworkNFT is PatchworkNFTInterfaceMeta, IERC5192 { + /** + @notice Emitted when the freeze status is changed to frozen. + @param tokenId The identifier for a token. + */ + event Frozen(uint256 indexed tokenId); + + /** + @notice Emitted when the locking status is changed to not frozen. + @param tokenId The identifier for a token. + */ + event Thawed(uint256 indexed tokenId); + + /** + @notice Emitted when the permissions are changed for an NFT + @param to The address the permissions are assigned to + @param permissions The permissions + */ + event PermissionChange(address indexed to, uint256 permissions); + + /** + @notice Emitted when the schema has changed for an NFT + @param addr the address of the NFT + */ + event SchemaChange(address indexed addr); + + /** + @notice Get the scope this NFT claims to belong to + @return string the name of the scope + */ + function getScopeName() external returns (string memory); + + /** + @notice Returns the URI of the schema + @return string the URI of the schema + */ + function schemaURI() external returns (string memory); + + /** + @notice Returns the metadata schema + @return MetadataSchema the metadata schema + */ + function schema() external returns (MetadataSchema memory); + + /** + @notice Returns the URI of the image associated with the given token ID + @param tokenId ID of the token + @return string the image URI + */ + function imageURI(uint256 tokenId) external returns (string memory); + + /** + @notice Sets permissions for a given address + @param to Address to set permissions for + @param permissions Permissions value + */ + function setPermissions(address to, uint256 permissions) external; + + /** + @notice Stores packed metadata for a given token ID and slot + @param tokenId ID of the token + @param slot Slot to store metadata + @param data Metadata to store + */ + function storePackedMetadataSlot(uint256 tokenId, uint256 slot, uint256 data) external; + + /** + @notice Loads packed metadata for a given token ID and slot + @param tokenId ID of the token + @param slot Slot to load metadata from + @return uint256 the raw slot data as a uint256 + */ + function loadPackedMetadataSlot(uint256 tokenId, uint256 slot) external returns (uint256); + + /** + @notice Returns the freeze nonce for a given token ID + @param tokenId ID of the token + @return nonce the nonce + */ + function getFreezeNonce(uint256 tokenId) external returns (uint256 nonce); + + /** + @notice Sets the freeze status of a token + @param tokenId ID of the token + @param frozen Freeze status to set + */ + function setFrozen(uint256 tokenId, bool frozen) external; + + /** + @notice Gets the freeze status of a token (ERC-5192) + @param tokenId ID of the token + @return bool true if frozen, false if not + */ + function frozen(uint256 tokenId) external view returns (bool); + + /** + @notice Sets the lock status of a token + @param tokenId ID of the token + @param locked Lock status to set + */ + function setLocked(uint256 tokenId, bool locked) external; +} \ No newline at end of file diff --git a/src/IPatchworkPatch.sol b/src/IPatchworkPatch.sol new file mode 100644 index 0000000..0b92ed4 --- /dev/null +++ b/src/IPatchworkPatch.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/** +@title Patchwork Protocol Patch Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting Patchwork patch standard +*/ +interface IPatchworkPatch { + /** + @notice Get the scope this NFT claims to belong to + @return string the name of the scope + */ + function getScopeName() external returns (string memory); + + /** + @notice Creates a new token for the owner, representing a patch + @param owner Address of the owner of the token + @param originalNFTAddress Address of the original NFT + @param originalNFTTokenId ID of the original NFT token + @return tokenId ID of the newly minted token + */ + function mintPatch(address owner, address originalNFTAddress, uint256 originalNFTTokenId) external returns (uint256 tokenId); + + /** + @notice Updates the real underlying ownership of a token in storage (if different from current) + @param tokenId ID of the token + */ + function updateOwnership(uint256 tokenId) external; + + /** + @notice Returns the underlying stored owner of a token ignoring real patched NFT ownership + @param tokenId ID of the token + @return address Address of the owner + */ + function unpatchedOwnerOf(uint256 tokenId) external returns (address); + + /** + @notice A deliberately incompatible function to block implementing both assignable and patch + @return bytes1 Always returns 0x00 + */ + function patchworkCompatible_() external pure returns (bytes1); +} \ No newline at end of file diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol new file mode 100644 index 0000000..700ac8c --- /dev/null +++ b/src/IPatchworkProtocol.sol @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/** +@title Patchwork Protocol Interface +@author Runic Labs, Inc +@notice Interface for Patchwork Protocol +*/ +interface IPatchworkProtocol { + /** + @notice The address is not authorized to perform this action + @param addr The address attempting to perform the action + */ + error NotAuthorized(address addr); + + /** + @notice The scope with the provided name already exists + @param scopeName Name of the scope + */ + error ScopeExists(string scopeName); + + /** + @notice The scope with the provided name does not exist + @param scopeName Name of the scope + */ + error ScopeDoesNotExist(string scopeName); + + /** + @notice Transfer of the scope to the provided address is not allowed + @param to Address not allowed for scope transfer + */ + error ScopeTransferNotAllowed(address to); + + /** + @notice The token with the provided ID at the given address is frozen + @param addr Address of the token owner + @param tokenId ID of the frozen token + */ + error Frozen(address addr, uint256 tokenId); + + /** + @notice The token with the provided ID at the given address is locked + @param addr Address of the token owner + @param tokenId ID of the locked token + */ + error Locked(address addr, uint256 tokenId); + + /** + @notice The address is not whitelisted for the given scope + @param scopeName Name of the scope + @param addr Address that isn't whitelisted + */ + error NotWhitelisted(string scopeName, address addr); + + /** + @notice The address at the given address has already been patched + @param addr The address that was patched + @param patchAddress Address of the patch applied + */ + error AccountAlreadyPatched(address addr, address patchAddress); + + /** + @notice The token at the given address has already been patched + @param addr Address of the token owner + @param tokenId ID of the patched token + @param patchAddress Address of the patch applied + */ + error AlreadyPatched(address addr, uint256 tokenId, address patchAddress); + + /** + @notice The provided input lengths are not compatible or valid + @dev for any multi array inputs, they must be the same length + */ + error BadInputLengths(); + + /** + @notice The fragment at the given address is unregistered + @param addr Address of the unregistered fragment + */ + error FragmentUnregistered(address addr); + + /** + @notice The fragment at the given address has been redacted + @param addr Address of the redacted fragment + */ + error FragmentRedacted(address addr); + + /** + @notice The fragment with the provided ID at the given address is already assigned + @param addr Address of the fragment + @param tokenId ID of the assigned fragment + */ + error FragmentAlreadyAssigned(address addr, uint256 tokenId); + + /** + @notice The fragment with the provided ID at the given address is already assigned in the scope + @param scopeName Name of the scope + @param addr Address of the fragment + @param tokenId ID of the fragment + */ + error FragmentAlreadyAssignedInScope(string scopeName, address addr, uint256 tokenId); + + /** + @notice The reference was not found in the scope for the given fragment and target + @param scopeName Name of the scope + @param target Address of the target token + @param fragment Address of the fragment + @param tokenId ID of the fragment + */ + error RefNotFoundInScope(string scopeName, address target, address fragment, uint256 tokenId); + + /** + @notice The fragment with the provided ID at the given address is not assigned + @param addr Address of the fragment + @param tokenId ID of the fragment + */ + error FragmentNotAssigned(address addr, uint256 tokenId); + + /** + @notice The fragment at the given address is already registered + @param addr Address of the registered fragment + */ + error FragmentAlreadyRegistered(address addr); + + /** + @notice Ran out of available IDs for allocation + @dev Max 255 IDs per NFT + */ + error OutOfIDs(); + + /** + @notice The provided token ID is unsupported + @dev TokenIds may only be 56 bits long + @param tokenId The unsupported token ID + */ + error UnsupportedTokenId(uint256 tokenId); + + /** + @notice Cannot lock the soulbound patch at the given address + @param addr Address of the soulbound patch + */ + error CannotLockSoulboundPatch(address addr); + + /** + @notice The token with the provided ID at the given address is not frozen + @param addr Address of the token owner + @param tokenId ID of the token + */ + error NotFrozen(address addr, uint256 tokenId); + + /** + @notice The nonce for the token with the provided ID at the given address is incorrect + @dev It may be incorrect or a newer nonce may be present + @param addr Address of the token owner + @param tokenId ID of the token + @param nonce The incorrect nonce + */ + error IncorrectNonce(address addr, uint256 tokenId, uint256 nonce); + + /** + @notice Self assignment of the token with the provided ID at the given address is not allowed + @param addr Address of the token owner + @param tokenId ID of the token + */ + error SelfAssignmentNotAllowed(address addr, uint256 tokenId); + + /** + @notice Transfer of the token with the provided ID at the given address is not allowed + @param addr Address of the token owner + @param tokenId ID of the token + */ + error TransferNotAllowed(address addr, uint256 tokenId); + + /** + @notice Transfer of the token with the provided ID at the given address is blocked by an assignment + @param addr Address of the token owner + @param tokenId ID of the token + */ + error TransferBlockedByAssignment(address addr, uint256 tokenId); + + /** + @notice A rule is blocking the mint to this owner address + @param addr Address of the token owner + */ + error MintNotAllowed(address addr); + + /** + @notice The token at the given address is not IPatchworkAssignable + @param addr Address of the non-assignable token + */ + error NotPatchworkAssignable(address addr); + + /** + @notice A data integrity error has been detected + @dev Addr+TokenId is expected where addr2+tokenId2 is present + @param addr Address of the first token + @param tokenId ID of the first token + @param addr2 Address of the second token + @param tokenId2 ID of the second token + */ + error DataIntegrityError(address addr, uint256 tokenId, address addr2, uint256 tokenId2); + + /** + @notice The operation is not supported + */ + error UnsupportedOperation(); + + /** + @notice Represents a defined scope within the system + @dev Contains details about the scope ownership, permissions, and mappings for references and assignments + */ + struct Scope { + /** + @notice Owner of this scope + @dev Address of the account or contract that owns this scope + */ + address owner; + + /** + @notice Owner-elect + @dev Used in two-step transfer process. If this is set, only this owner can accept the transfer + */ + address ownerElect; + + /** + @notice Indicates whether a user is allowed to patch within this scope + @dev True if a user can patch, false otherwise. If false, only operators and the scope owner can perform patching. + */ + bool allowUserPatch; + + /** + @notice Indicates whether a user is allowed to assign within this scope + @dev True if a user can assign, false otherwise. If false, only operators and the scope owner can perform assignments. + */ + bool allowUserAssign; + + /** + @notice Indicates if a whitelist is required for operations within this scope + @dev True if whitelist is required, false otherwise + */ + bool requireWhitelist; + + /** + @notice Mapped list of operator addresses for this scope + @dev Address of the operator mapped to a boolean indicating if they are an operator + */ + mapping(address => bool) operators; + + /** + @notice Mapped list of lightweight references within this scope + @dev A hash of liteRefAddr + reference provides uniqueness + */ + mapping(bytes32 => bool) liteRefs; + + /** + @notice Mapped whitelist of addresses that belong to this scope + @dev Address mapped to a boolean indicating if it's whitelisted + */ + mapping(address => bool) whitelist; + + /** + @notice Mapped list of unique patches associated with this scope + @dev Hash of the patch mapped to a boolean indicating its uniqueness + */ + mapping(bytes32 => bool) uniquePatches; + } + + /** + @notice Emitted when a fragment is assigned + @param owner The owner of the target and fragment + @param fragmentAddress The address of the fragment's contract + @param fragmentTokenId The tokenId of the fragment + @param targetAddress The address of the target's contract + @param targetTokenId The tokenId of the target + */ + event Assign(address indexed owner, address fragmentAddress, uint256 fragmentTokenId, address indexed targetAddress, uint256 indexed targetTokenId); + + /** + @notice Emitted when a fragment is unassigned + @param owner The owner of the fragment + @param fragmentAddress The address of the fragment's contract + @param fragmentTokenId The tokenId of the fragment + @param targetAddress The address of the target's contract + @param targetTokenId The tokenId of the target + */ + event Unassign(address indexed owner, address fragmentAddress, uint256 fragmentTokenId, address indexed targetAddress, uint256 indexed targetTokenId); + + /** + @notice Emitted when a patch is minted + @param owner The owner of the patch + @param originalAddress The address of the original NFT's contract + @param originalTokenId The tokenId of the original NFT + @param patchAddress The address of the patch's contract + @param patchTokenId The tokenId of the patch + */ + event Patch(address indexed owner, address originalAddress, uint256 originalTokenId, address indexed patchAddress, uint256 indexed patchTokenId); + + /** + @notice Emitted when an account patch is minted + @param owner The owner of the patch + @param originalAddress The address of the original NFT's contract + @param patchAddress The address of the patch's contract + @param patchTokenId The tokenId of the patch + */ + event AccountPatch(address indexed owner, address originalAddress, address indexed patchAddress, uint256 indexed patchTokenId); + + /** + @notice Emitted when a new scope is claimed + @param scopeName The name of the claimed scope + @param owner The owner of the scope + */ + event ScopeClaim(string indexed scopeName, address indexed owner); + + /** + @notice Emitted when a scope has elected a new owner to transfer to + @param scopeName The name of the transferred scope + @param from The owner of the scope + @param to The owner-elect of the scope + */ + event ScopeTransferElect(string indexed scopeName, address indexed from, address indexed to); + + /** + @notice Emitted when a scope transfer is canceled + @param scopeName The name of the transferred scope + @param from The owner of the scope + @param to The owner-elect of the scope + */ + event ScopeTransferCancel(string indexed scopeName, address indexed from, address indexed to); + + /** + @notice Emitted when a scope is transferred + @param scopeName The name of the transferred scope + @param from The address transferring the scope + @param to The recipient of the scope + */ + event ScopeTransfer(string indexed scopeName, address indexed from, address indexed to); + + /** + @notice Emitted when a scope has an operator added + @param scopeName The name of the scope + @param actor The address responsible for the action + @param operator The new operator's address + */ + event ScopeAddOperator(string indexed scopeName, address indexed actor, address indexed operator); + + /** + @notice Emitted when a scope has an operator removed + @param scopeName The name of the scope + @param actor The address responsible for the action + @param operator The operator's address being removed + */ + event ScopeRemoveOperator(string indexed scopeName, address indexed actor, address indexed operator); + + /** + @notice Emitted when a scope's rules are changed + @param scopeName The name of the scope + @param actor The address responsible for the action + @param allowUserPatch Indicates whether user patches are allowed + @param allowUserAssign Indicates whether user assignments are allowed + @param requireWhitelist Indicates whether a whitelist is required + */ + event ScopeRuleChange(string indexed scopeName, address indexed actor, bool allowUserPatch, bool allowUserAssign, bool requireWhitelist); + + /** + @notice Emitted when a scope has an address added to the whitelist + @param scopeName The name of the scope + @param actor The address responsible for the action + @param addr The address being added to the whitelist + */ + event ScopeWhitelistAdd(string indexed scopeName, address indexed actor, address indexed addr); + + /** + @notice Emitted when a scope has an address removed from the whitelist + @param scopeName The name of the scope + @param actor The address responsible for the action + @param addr The address being removed from the whitelist + */ + event ScopeWhitelistRemove(string indexed scopeName, address indexed actor, address indexed addr); + + /** + @notice Claim a scope + @param scopeName the name of the scope + */ + function claimScope(string calldata scopeName) external; + + /** + @notice Transfer ownership of a scope + @dev must be accepted by transferee - see {acceptScopeTransfer} + @param scopeName Name of the scope + @param newOwner Address of the new owner + */ + function transferScopeOwnership(string calldata scopeName, address newOwner) external; + + /** + @notice Cancel a pending scope transfer + @param scopeName Name of the scope + */ + function cancelScopeTransfer(string calldata scopeName) external; + + /** + @notice Accept a scope transfer + @param scopeName Name of the scope + */ + function acceptScopeTransfer(string calldata scopeName) external; + + /** + @notice Get owner-elect of a scope + @param scopeName Name of the scope + @return ownerElect Address of the scope's owner-elect + */ + function getScopeOwnerElect(string calldata scopeName) external returns (address ownerElect); + + /** + @notice Get owner of a scope + @param scopeName Name of the scope + @return owner Address of the scope owner + */ + function getScopeOwner(string calldata scopeName) external returns (address owner); + + /** + @notice Add an operator to a scope + @param scopeName Name of the scope + @param op Address of the operator + */ + function addOperator(string calldata scopeName, address op) external; + + /** + @notice Remove an operator from a scope + @param scopeName Name of the scope + @param op Address of the operator + */ + function removeOperator(string calldata scopeName, address op) external; + + /** + @notice Set rules for a scope + @param scopeName Name of the scope + @param allowUserPatch Boolean indicating whether user patches are allowed + @param allowUserAssign Boolean indicating whether user assignments are allowed + @param requireWhitelist Boolean indicating whether whitelist is required + */ + function setScopeRules(string calldata scopeName, bool allowUserPatch, bool allowUserAssign, bool requireWhitelist) external; + + /** + @notice Add an address to a scope's whitelist + @param scopeName Name of the scope + @param addr Address to be whitelisted + */ + function addWhitelist(string calldata scopeName, address addr) external; + + /** + @notice Remove an address from a scope's whitelist + @param scopeName Name of the scope + @param addr Address to be removed from the whitelist + */ + function removeWhitelist(string calldata scopeName, address addr) external; + + /** + @notice Create a new patch + @param originalNFTAddress Address of the original NFT + @param originalNFTTokenId Token ID of the original NFT + @param patchAddress Address of the IPatchworkPatch to mint + @return tokenId Token ID of the newly created patch + */ + function createPatch(address originalNFTAddress, uint originalNFTTokenId, address patchAddress) external returns (uint256 tokenId); + + /** + @notice Create a new account patch + @param originalAddress Address of the original account + @param patchAddress Address of the IPatchworkPatch to mint + @return tokenId Token ID of the newly created patch + */ + function createAccountPatch(address owner, address originalAddress, address patchAddress) external returns (uint256 tokenId); + + /** + @notice Assigns an NFT relation to have an IPatchworkLiteRef form a LiteRef to a IPatchworkAssignableNFT + @param fragment The IPatchworkAssignableNFT address to assign + @param fragmentTokenId The IPatchworkAssignableNFT Token ID to assign + @param target The IPatchworkLiteRef address to hold the reference to the fragment + @param targetTokenId The IPatchworkLiteRef Token ID to hold the reference to the fragment + */ + function assignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external; + + /** + @notice Assign multiple NFT fragments to a target NFT in batch + @param fragments The array of addresses of the fragment IPatchworkAssignableNFTs + @param tokenIds The array of token IDs of the fragment IPatchworkAssignableNFTs + @param target The address of the target IPatchworkLiteRef NFT + @param targetTokenId The token ID of the target IPatchworkLiteRef NFT + */ + function batchAssignNFT(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) external; + + /** + @notice Unassign a NFT fragment from a target NFT + @param fragment The IPatchworkAssignableNFT address of the fragment NFT + @param fragmentTokenId The IPatchworkAssignableNFT token ID of the fragment NFT + */ + function unassignNFT(address fragment, uint fragmentTokenId) external; + + /** + @notice Apply transfer rules and actions of a specific token from one address to another + @param from The address of the sender + @param to The address of the receiver + @param tokenId The ID of the token to be transferred + */ + function applyTransfer(address from, address to, uint256 tokenId) external; + + /** + @notice Update the ownership tree of a specific Patchwork NFT + @param nft The address of the Patchwork NFT + @param tokenId The ID of the token whose ownership tree needs to be updated + */ + function updateOwnershipTree(address nft, uint256 tokenId) external; +} \ No newline at end of file diff --git a/src/PatchworkAccountPatch.sol b/src/PatchworkAccountPatch.sol index a8fd506..3c39341 100644 --- a/src/PatchworkAccountPatch.sol +++ b/src/PatchworkAccountPatch.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.13; import "./IPatchworkAccountPatch.sol"; -import "./PatchworkNFTBase.sol"; -import "./PatchworkProtocol.sol"; +import "./IPatchworkProtocol.sol"; +import "./PatchworkNFT.sol"; /** @title PatchworkAccountPatch @@ -44,7 +44,7 @@ abstract contract PatchworkAccountPatch is PatchworkNFT, IPatchworkAccountPatch @dev See {ERC721-_burn} */ function _burn(uint256 /*tokenId*/) internal virtual override { - revert PatchworkProtocol.UnsupportedOperation(); + revert IPatchworkProtocol.UnsupportedOperation(); } /** diff --git a/src/PatchworkFragment.sol b/src/PatchworkFragment.sol new file mode 100644 index 0000000..fd827d6 --- /dev/null +++ b/src/PatchworkFragment.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "./PatchworkNFT.sol"; +import "./IPatchworkAssignableNFT.sol"; + +/** +@title PatchworkFragment +@dev base implementation of a Fragment is IPatchworkAssignableNFT +*/ +abstract contract PatchworkFragment is PatchworkNFT, IPatchworkAssignableNFT { + + /// Represents an assignment of a token from an external NFT contract to a token in this contract. + struct Assignment { + address tokenAddr; /// The address of the external NFT contract. + uint256 tokenId; /// The ID of the token in the external NFT contract. + } + + /// A mapping from token IDs in this contract to their assignments. + mapping(uint256 => Assignment) internal _assignments; + + /** + @dev See {IPatchworkNFT-getScopeName} + */ + function getScopeName() public view virtual override (IPatchworkAssignableNFT, PatchworkNFT) returns (string memory) { + return _scopeName; + } + + /** + @dev See {IERC165-supportsInterface} + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(IPatchworkAssignableNFT).interfaceId || + super.supportsInterface(interfaceID); + } + + /** + @dev See {IPatchworkAssignableNFT-assign} + */ + function assign(uint256 ourTokenId, address to, uint256 tokenId) public virtual mustHaveTokenWriteAuth(tokenId) { + // One time use policy + Assignment storage a = _assignments[ourTokenId]; + if (a.tokenAddr != address(0)) { + revert IPatchworkProtocol.FragmentAlreadyAssigned(address(this), ourTokenId); + } + a.tokenAddr = to; + a.tokenId = tokenId; + emit Locked(ourTokenId); + } + + /** + @dev See {IPatchworkAssignableNFT-unassign} + */ + function unassign(uint256 tokenId) public virtual mustHaveTokenWriteAuth(tokenId) { + updateOwnership(tokenId); + delete _assignments[tokenId]; + emit Unlocked(tokenId); + } + + /** + @dev See {IPatchworkAssignableNFT-updateOwnership} + */ + function updateOwnership(uint256 tokenId) public virtual { + Assignment storage assignment = _assignments[tokenId]; + if (assignment.tokenAddr != address(0)) { + address owner_ = ownerOf(tokenId); + address curOwner = super.ownerOf(tokenId); + if (owner_ != curOwner) { + // Parent ownership has changed, update our ownership to reflect this + ERC721._transfer(curOwner, owner_, tokenId); + } + } + } + + /** + @dev owned by the assignment's owner + @dev See {IERC721-ownerOf} + */ + function ownerOf(uint256 tokenId) public view virtual override(ERC721, IERC721) returns (address) { + // If assigned, it's owned by the assignment, otherwise normal owner + Assignment storage assignment = _assignments[tokenId]; + if (assignment.tokenAddr != address(0)) { + return IERC721(assignment.tokenAddr).ownerOf(assignment.tokenId); + } + return super.ownerOf(tokenId); + } + + /** + @dev See {IPatchworkAssignableNFT-unassignedOwnerOf} + */ + function unassignedOwnerOf(uint256 tokenId) public virtual view returns (address) { + return super.ownerOf(tokenId); + } + + /** + @dev See {IPatchworkAssignableNFT-getAssignedTo} + */ + function getAssignedTo(uint256 ourTokenId) public virtual view returns (address, uint256) { + Assignment storage a = _assignments[ourTokenId]; + return (a.tokenAddr, a.tokenId); + } + + /** + @dev See {IPatchworkAssignableNFT-onAssignedTransfer} + */ + function onAssignedTransfer(address from, address to, uint256 tokenId) public virtual { + require(msg.sender == _manager); + emit Transfer(from, to, tokenId); + } + + /** + @dev See {IPatchworkNFT-locked} + */ + function locked(uint256 tokenId) public view virtual override returns (bool) { + // Locked when assigned (implicit) or if explicitly locked + return _assignments[tokenId].tokenAddr != address(0) || super.locked(tokenId); + } + + /** + @dev See {IPatchworkNFT-setLocked} + */ + function setLocked(uint256 tokenId, bool locked_) public virtual override { + if (msg.sender != ownerOf(tokenId)) { + revert IPatchworkProtocol.NotAuthorized(msg.sender); + } + require(_assignments[tokenId].tokenAddr == address(0), "cannot setLocked assigned fragment"); + super.setLocked(tokenId, locked_); + } + + /** + @dev See {IPatchworkNFT-patchworkCompatible_} + */ + function patchworkCompatible_() external pure returns (bytes2) {} +} \ No newline at end of file diff --git a/src/PatchworkLiteRef.sol b/src/PatchworkLiteRef.sol new file mode 100644 index 0000000..378aa85 --- /dev/null +++ b/src/PatchworkLiteRef.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "./IPatchworkLiteRef.sol"; +import "./IPatchworkProtocol.sol"; + +/** +@title PatchworkLiteRef +@dev base implementation of IPatchworkLiteRef +*/ +abstract contract PatchworkLiteRef is IPatchworkLiteRef, ERC165 { + + /// A mapping from reference IDs to their associated addresses. + mapping(uint8 => address) internal _referenceAddresses; + + /// A reverse mapping from addresses to their corresponding reference IDs. + mapping(address => uint8) internal _referenceAddressIds; + + /// A mapping indicating which reference IDs have been redacted. + mapping(uint8 => bool) internal _redactedReferenceIds; + + /// The ID that will be used for the next reference added. + uint8 internal _nextReferenceId; + + /** + @dev Constructor for the PatchworkLiteRef contract. Initializes the next reference ID to 1 to differentiate unregistered references. + */ + constructor() { + _nextReferenceId = 1; // Start at 1 so we can identify if we already have one registered + } + + /** + @notice implements a permission check for functions of this abstract class to use + @return allow true if write is allowed, false if not + */ + function _checkWriteAuth() internal virtual returns (bool allow); + + /** + @dev See {IERC165-supportsInterface} + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(IPatchworkLiteRef).interfaceId || + ERC165.supportsInterface(interfaceID); + } + + /** + @dev See {IPatchworkLiteRef-registerReferenceAddress} + */ + function registerReferenceAddress(address addr) public virtual _mustHaveWriteAuth returns (uint8 id) { + uint8 refId = _nextReferenceId; + if (_nextReferenceId == 255) { + revert IPatchworkProtocol.OutOfIDs(); + } + _nextReferenceId++; + if (_referenceAddressIds[addr] != 0) { + revert IPatchworkProtocol.FragmentAlreadyRegistered(addr); + } + _referenceAddresses[refId] = addr; + _referenceAddressIds[addr] = refId; + emit Register(address(this), addr, refId); + return refId; + } + + /** + @dev See {IPatchworkLiteRef-getReferenceId} + */ + function getReferenceId(address addr) public virtual returns (uint8 id, bool redacted) { + uint8 refId = _referenceAddressIds[addr]; + return (refId, _redactedReferenceIds[refId]); + } + + /** + @dev See {IPatchworkLiteRef-getReferenceAddress} + */ + function getReferenceAddress(uint8 id) public virtual returns (address addr, bool redacted) { + return (_referenceAddresses[id], _redactedReferenceIds[id]); + } + + /** + @dev See {IPatchworkLiteRef-redactReferenceAddress} + */ + function redactReferenceAddress(uint8 id) public virtual _mustHaveWriteAuth { + _redactedReferenceIds[id] = true; + emit Redact(address(this), _referenceAddresses[id]); + } + + /** + @dev See {IPatchworkLiteRef-unredactReferenceAddress} + */ + function unredactReferenceAddress(uint8 id) public virtual _mustHaveWriteAuth { + _redactedReferenceIds[id] = false; + emit Unredact(address(this), _referenceAddresses[id]); + } + + /** + @dev See {IPatchworkLiteRef-getLiteReference} + */ + function getLiteReference(address addr, uint256 tokenId) public virtual view returns (uint64 referenceAddress, bool redacted) { + uint8 refId = _referenceAddressIds[addr]; + if (refId == 0) { + return (0, false); + } + if (tokenId > type(uint56).max) { + revert IPatchworkProtocol.UnsupportedTokenId(tokenId); + } + return (uint64(uint256(refId) << 56 | tokenId), _redactedReferenceIds[refId]); + } + + /** + @dev See {IPatchworkLiteRef-getReferenceAddressAndTokenId} + */ + function getReferenceAddressAndTokenId(uint64 referenceAddress) public virtual view returns (address addr, uint256 tokenId) { + // <8 bits of refId, 56 bits of tokenId> + uint8 refId = uint8(referenceAddress >> 56); + tokenId = referenceAddress & 0x00FFFFFFFFFFFFFF; // 64 bit mask + return (_referenceAddresses[refId], tokenId); + } + + modifier _mustHaveWriteAuth { + if (!_checkWriteAuth()) { + revert IPatchworkProtocol.NotAuthorized(msg.sender); + } + _; + } +} \ No newline at end of file diff --git a/src/PatchworkNFT.sol b/src/PatchworkNFT.sol new file mode 100644 index 0000000..55958f2 --- /dev/null +++ b/src/PatchworkNFT.sol @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "./IPatchworkNFT.sol"; +import "./IERC4906.sol"; +import "./IPatchworkProtocol.sol"; + +/** +@title PatchworkNFT Abstract Contract +@dev This abstract contract defines the core functionalities for the PatchworkNFT. + It inherits from the standard ERC721, as well as the IPatchworkNFT and IERC4906 interfaces. +*/ +abstract contract PatchworkNFT is ERC721, IPatchworkNFT, IERC4906 { + + /// @dev The scope name for the NFT. + string internal _scopeName; + + /// @dev The address that denotes the owner of the contract. + address internal _owner; + + /// @dev The address that manages the NFTs (PatchworkProtocol). + address internal _manager; + + /// @dev A mapping to keep track of permissions for each address. + mapping(address => uint256) internal _permissionsAllow; + + /// @dev A mapping for storing metadata associated with each NFT token ID. + mapping(uint256 => uint256[]) internal _metadataStorage; + + /// @dev A mapping for storing freeze nonces of each NFT token ID. + mapping(uint256 => uint256) internal _freezeNonces; + + /// @dev A mapping indicating whether a specific NFT token ID is frozen. + mapping(uint256 => bool) internal _freezes; + + /// @dev A mapping indicating whether a specific NFT token ID is locked. + mapping(uint256 => bool) internal _locks; + + /** + * @notice Creates a new instance of the PatchworkNFT contract with the provided parameters. + * @param scopeName_ The scope name for the NFT. + * @param name_ The ERC-721 name for the NFT. + * @param symbol_ The ERC-721 symbol for the NFT. + * @param owner_ The address that will be set as the owner. + * @param manager_ The address that will be set as the manager (PatchworkProtocol). + */ + constructor( + string memory scopeName_, + string memory name_, + string memory symbol_, + address owner_, + address manager_ + ) ERC721(name_, symbol_) { + _scopeName = scopeName_; + _owner = owner_; + _manager = manager_; + } + + /** + @dev See {IPatchworkNFT-getScopeName} + */ + function getScopeName() public view virtual returns (string memory) { + return _scopeName; + } + + /** + @dev See {IPatchworkNFT-storePackedMetadataSlot} + */ + function storePackedMetadataSlot(uint256 tokenId, uint256 slot, uint256 data) public virtual mustHaveTokenWriteAuth(tokenId) { + _metadataStorage[tokenId][slot] = data; + } + + /** + @dev See {IPatchworkNFT-loadPackedMetadataSlot} + */ + function loadPackedMetadataSlot(uint256 tokenId, uint256 slot) public virtual view returns (uint256) { + return _metadataStorage[tokenId][slot]; + } + + // Does msg.sender have permission to write to our top level storage? + function _checkWriteAuth() internal virtual view returns (bool allow) { + return (msg.sender == _owner); + } + + // Does msg.sender have permission to write to this token's data? + function _checkTokenWriteAuth(uint256 /*tokenId*/) internal virtual view returns (bool allow) { + return (msg.sender == _owner || msg.sender == _manager); + } + + /** + @dev See {IPatchworkNFT-setPermissions} + */ + function setPermissions(address to, uint256 permissions) public virtual mustHaveWriteAuth { + _permissionsAllow[to] = permissions; + emit PermissionChange(to, permissions); + } + + /** + @dev See {IERC165-supportsInterface} + */ + function supportsInterface(bytes4 interfaceID) public view virtual override(ERC721, IERC165) returns (bool) { + return interfaceID == type(IPatchworkNFT).interfaceId || + interfaceID == type(IERC5192).interfaceId || + interfaceID == type(IERC4906).interfaceId || + ERC721.supportsInterface(interfaceID); + } + + /** + @dev See {IERC721-transferFrom}. + */ + function transferFrom(address from, address to, uint256 tokenId) public virtual override(ERC721, IERC721) { + IPatchworkProtocol(_manager).applyTransfer(from, to, tokenId); + super.transferFrom(from, to, tokenId); + } + + /** + @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override(ERC721, IERC721) { + IPatchworkProtocol(_manager).applyTransfer(from, to, tokenId); + super.safeTransferFrom(from, to, tokenId); + } + + /** + @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override(ERC721, IERC721) { + IPatchworkProtocol(_manager).applyTransfer(from, to, tokenId); + super.safeTransferFrom(from, to, tokenId, data); + } + + /** + @notice transfers a token with a known freeze nonce + @dev reverts if the token is not frozen or if the current freeze nonce does not match the provided nonce + @dev See {IERC721-transferFrom}. + */ + function transferFromWithFreezeNonce(address from, address to, uint256 tokenId, uint256 nonce) public mustBeFrozenWithNonce(tokenId, nonce) { + transferFrom(from, to, tokenId); + } + + /** + @notice transfers a token with a known freeze nonce + @dev reverts if the token is not frozen or if the current freeze nonce does not match the provided nonce + @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFromWithFreezeNonce(address from, address to, uint256 tokenId, uint256 nonce) public mustBeFrozenWithNonce(tokenId, nonce) { + safeTransferFrom(from, to, tokenId); + } + + /** + @notice transfers a token with a known freeze nonce + @dev reverts if the token is not frozen or if the current freeze nonce does not match the provided nonce + @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFromWithFreezeNonce(address from, address to, uint256 tokenId, bytes memory data, uint256 nonce) public mustBeFrozenWithNonce(tokenId, nonce) { + safeTransferFrom(from, to, tokenId, data); + } + + /** + @dev See {IPatchworkNFT-getFreezeNonce} + */ + function getFreezeNonce(uint256 tokenId) public view virtual returns (uint256 nonce) { + return _freezeNonces[tokenId]; + } + + /** + @dev See {IPatchworkNFT-setFrozen} + */ + function setFrozen(uint256 tokenId, bool frozen_) public virtual mustBeTokenOwner(tokenId) { + bool _frozen = _freezes[tokenId]; + if (_frozen != frozen_) { + if (frozen_) { + _freezes[tokenId] = true; + emit Frozen(tokenId); + } else { + _freezeNonces[tokenId]++; + _freezes[tokenId] = false; + emit Thawed(tokenId); + } + } + } + + /** + @dev See {IPatchworkNFT-frozen} + */ + function frozen(uint256 tokenId) public view virtual returns (bool) { + return _freezes[tokenId]; + } + + /** + @dev See {IPatchworkNFT-locked} + */ + function locked(uint256 tokenId) public view virtual returns (bool) { + return _locks[tokenId]; + } + + /** + @dev See {IPatchworkNFT-setLocked} + */ + function setLocked(uint256 tokenId, bool locked_) public virtual mustBeTokenOwner(tokenId) { + bool _locked = _locks[tokenId]; + if (_locked != locked_) { + _locks[tokenId] = locked_; + if (locked_) { + emit Locked(tokenId); + } else { + emit Unlocked(tokenId); + } + } + } + + modifier mustHaveWriteAuth { + if (!_checkWriteAuth()) { + revert IPatchworkProtocol.NotAuthorized(msg.sender); + } + _; + } + + modifier mustHaveTokenWriteAuth(uint256 tokenId) { + if (!_checkTokenWriteAuth(tokenId)) { + revert IPatchworkProtocol.NotAuthorized(msg.sender); + } + _; + } + + modifier mustBeTokenOwner(uint256 tokenId) { + if (msg.sender != ownerOf(tokenId)) { + revert IPatchworkProtocol.NotAuthorized(msg.sender); + } + _; + } + + modifier mustBeFrozenWithNonce(uint256 tokenId, uint256 nonce) { + if (!frozen(tokenId)) { + revert IPatchworkProtocol.NotFrozen(address(this), tokenId); + } + if (getFreezeNonce(tokenId) != nonce) { + revert IPatchworkProtocol.IncorrectNonce(address(this), tokenId, nonce); + } + _; + } +} \ No newline at end of file diff --git a/src/PatchworkNFTBase.sol b/src/PatchworkNFTBase.sol deleted file mode 100644 index 48175c3..0000000 --- a/src/PatchworkNFTBase.sol +++ /dev/null @@ -1,592 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "./PatchworkNFTInterface.sol"; -import "./PatchworkProtocol.sol"; -import "./IERC4906.sol"; - -/** -@title PatchworkNFT Abstract Contract -@dev This abstract contract defines the core functionalities for the PatchworkNFT. - It inherits from the standard ERC721, as well as the IPatchworkNFT and IERC4906 interfaces. -*/ -abstract contract PatchworkNFT is ERC721, IPatchworkNFT, IERC4906 { - - /// @dev The scope name for the NFT. - string internal _scopeName; - - /// @dev The address that denotes the owner of the contract. - address internal _owner; - - /// @dev The address that manages the NFTs (PatchworkProtocol). - address internal _manager; - - /// @dev A mapping to keep track of permissions for each address. - mapping(address => uint256) internal _permissionsAllow; - - /// @dev A mapping for storing metadata associated with each NFT token ID. - mapping(uint256 => uint256[]) internal _metadataStorage; - - /// @dev A mapping for storing freeze nonces of each NFT token ID. - mapping(uint256 => uint256) internal _freezeNonces; - - /// @dev A mapping indicating whether a specific NFT token ID is frozen. - mapping(uint256 => bool) internal _freezes; - - /// @dev A mapping indicating whether a specific NFT token ID is locked. - mapping(uint256 => bool) internal _locks; - - /** - * @notice Creates a new instance of the PatchworkNFT contract with the provided parameters. - * @param scopeName_ The scope name for the NFT. - * @param name_ The ERC-721 name for the NFT. - * @param symbol_ The ERC-721 symbol for the NFT. - * @param owner_ The address that will be set as the owner. - * @param manager_ The address that will be set as the manager (PatchworkProtocol). - */ - constructor( - string memory scopeName_, - string memory name_, - string memory symbol_, - address owner_, - address manager_ - ) ERC721(name_, symbol_) { - _scopeName = scopeName_; - _owner = owner_; - _manager = manager_; - } - - /** - @dev See {IPatchworkNFT-getScopeName} - */ - function getScopeName() public view virtual returns (string memory) { - return _scopeName; - } - - /** - @dev See {IPatchworkNFT-storePackedMetadataSlot} - */ - function storePackedMetadataSlot(uint256 tokenId, uint256 slot, uint256 data) public virtual mustHaveTokenWriteAuth(tokenId) { - _metadataStorage[tokenId][slot] = data; - } - - /** - @dev See {IPatchworkNFT-loadPackedMetadataSlot} - */ - function loadPackedMetadataSlot(uint256 tokenId, uint256 slot) public virtual view returns (uint256) { - return _metadataStorage[tokenId][slot]; - } - - // Does msg.sender have permission to write to our top level storage? - function _checkWriteAuth() internal virtual view returns (bool allow) { - return (msg.sender == _owner); - } - - // Does msg.sender have permission to write to this token's data? - function _checkTokenWriteAuth(uint256 /*tokenId*/) internal virtual view returns (bool allow) { - return (msg.sender == _owner || msg.sender == _manager); - } - - /** - @dev See {IPatchworkNFT-setPermissions} - */ - function setPermissions(address to, uint256 permissions) public virtual mustHaveWriteAuth { - _permissionsAllow[to] = permissions; - emit PermissionChange(to, permissions); - } - - /** - @dev See {IERC165-supportsInterface} - */ - function supportsInterface(bytes4 interfaceID) public view virtual override(ERC721, IERC165) returns (bool) { - return interfaceID == type(IPatchworkNFT).interfaceId || - interfaceID == type(IERC5192).interfaceId || - interfaceID == type(IERC4906).interfaceId || - ERC721.supportsInterface(interfaceID); - } - - /** - @dev See {IERC721-transferFrom}. - */ - function transferFrom(address from, address to, uint256 tokenId) public virtual override(ERC721, IERC721) { - PatchworkProtocol(_manager).applyTransfer(from, to, tokenId); - super.transferFrom(from, to, tokenId); - } - - /** - @dev See {IERC721-safeTransferFrom}. - */ - function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override(ERC721, IERC721) { - PatchworkProtocol(_manager).applyTransfer(from, to, tokenId); - super.safeTransferFrom(from, to, tokenId); - } - - /** - @dev See {IERC721-safeTransferFrom}. - */ - function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override(ERC721, IERC721) { - PatchworkProtocol(_manager).applyTransfer(from, to, tokenId); - super.safeTransferFrom(from, to, tokenId, data); - } - - /** - @notice transfers a token with a known freeze nonce - @dev reverts if the token is not frozen or if the current freeze nonce does not match the provided nonce - @dev See {IERC721-transferFrom}. - */ - function transferFromWithFreezeNonce(address from, address to, uint256 tokenId, uint256 nonce) public mustBeFrozenWithNonce(tokenId, nonce) { - transferFrom(from, to, tokenId); - } - - /** - @notice transfers a token with a known freeze nonce - @dev reverts if the token is not frozen or if the current freeze nonce does not match the provided nonce - @dev See {IERC721-safeTransferFrom}. - */ - function safeTransferFromWithFreezeNonce(address from, address to, uint256 tokenId, uint256 nonce) public mustBeFrozenWithNonce(tokenId, nonce) { - safeTransferFrom(from, to, tokenId); - } - - /** - @notice transfers a token with a known freeze nonce - @dev reverts if the token is not frozen or if the current freeze nonce does not match the provided nonce - @dev See {IERC721-safeTransferFrom}. - */ - function safeTransferFromWithFreezeNonce(address from, address to, uint256 tokenId, bytes memory data, uint256 nonce) public mustBeFrozenWithNonce(tokenId, nonce) { - safeTransferFrom(from, to, tokenId, data); - } - - /** - @dev See {IPatchworkNFT-getFreezeNonce} - */ - function getFreezeNonce(uint256 tokenId) public view virtual returns (uint256 nonce) { - return _freezeNonces[tokenId]; - } - - /** - @dev See {IPatchworkNFT-setFrozen} - */ - function setFrozen(uint256 tokenId, bool frozen_) public virtual mustBeTokenOwner(tokenId) { - bool _frozen = _freezes[tokenId]; - if (_frozen != frozen_) { - if (frozen_) { - _freezes[tokenId] = true; - emit Frozen(tokenId); - } else { - _freezeNonces[tokenId]++; - _freezes[tokenId] = false; - emit Thawed(tokenId); - } - } - } - - /** - @dev See {IPatchworkNFT-frozen} - */ - function frozen(uint256 tokenId) public view virtual returns (bool) { - return _freezes[tokenId]; - } - - /** - @dev See {IPatchworkNFT-locked} - */ - function locked(uint256 tokenId) public view virtual returns (bool) { - return _locks[tokenId]; - } - - /** - @dev See {IPatchworkNFT-setLocked} - */ - function setLocked(uint256 tokenId, bool locked_) public virtual mustBeTokenOwner(tokenId) { - bool _locked = _locks[tokenId]; - if (_locked != locked_) { - _locks[tokenId] = locked_; - if (locked_) { - emit Locked(tokenId); - } else { - emit Unlocked(tokenId); - } - } - } - - modifier mustHaveWriteAuth { - if (!_checkWriteAuth()) { - revert PatchworkProtocol.NotAuthorized(msg.sender); - } - _; - } - - modifier mustHaveTokenWriteAuth(uint256 tokenId) { - if (!_checkTokenWriteAuth(tokenId)) { - revert PatchworkProtocol.NotAuthorized(msg.sender); - } - _; - } - - modifier mustBeTokenOwner(uint256 tokenId) { - if (msg.sender != ownerOf(tokenId)) { - revert PatchworkProtocol.NotAuthorized(msg.sender); - } - _; - } - - modifier mustBeFrozenWithNonce(uint256 tokenId, uint256 nonce) { - if (!frozen(tokenId)) { - revert PatchworkProtocol.NotFrozen(address(this), tokenId); - } - if (getFreezeNonce(tokenId) != nonce) { - revert PatchworkProtocol.IncorrectNonce(address(this), tokenId, nonce); - } - _; - } -} - -/** -@title PatchworkPatch -@dev Base implementation of IPatchworkPatch -@dev It is soul-bound to another ERC-721 and cannot be transferred or reassigned. -@dev It extends the functionalities of PatchworkNFT and implements the IPatchworkPatch interface. -*/ -abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { - - /// @dev Mapping from token ID to the address of the NFT that this patch is applied to. - mapping(uint256 => address) internal _patchedAddresses; - - /// @dev Mapping from token ID to the token ID of the NFT that this patch is applied to. - mapping(uint256 => uint256) internal _patchedTokenIds; - - /** - @dev See {IERC165-supportsInterface} - */ - function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { - return interfaceID == type(IPatchworkPatch).interfaceId || - super.supportsInterface(interfaceID); - } - - /** - @dev See {IPatchworkNFT-getScopeName} - */ - function getScopeName() public view virtual override(PatchworkNFT, IPatchworkPatch) returns (string memory) { - return _scopeName; - } - - /** - @dev will return the current owner of the patched address+tokenId - @dev See {IERC721-ownerOf} - */ - function ownerOf(uint256 tokenId) public view virtual override(ERC721, IERC721) returns (address) { - return IERC721(_patchedAddresses[tokenId]).ownerOf(_patchedTokenIds[tokenId]); - } - - /** - @notice stores a patch - @param tokenId the tokenId of the patch - @param originalNFTAddress the address of the original ERC-721 we are patching - @param originalNFTTokenId the tokenId of the original ERC-721 we are patching - */ - function _storePatch(uint256 tokenId, address originalNFTAddress, uint256 originalNFTTokenId) internal virtual { - _patchedAddresses[tokenId] = originalNFTAddress; - _patchedTokenIds[tokenId] = originalNFTTokenId; - } - - /** - @dev See {IPatchworkPatch-updateOwnership} - */ - function updateOwnership(uint256 tokenId) public virtual { - address patchedAddr = _patchedAddresses[tokenId]; - if (patchedAddr != address(0)) { - address owner_ = ownerOf(tokenId); - address curOwner = super.ownerOf(tokenId); - if (owner_ != curOwner) { - // Parent ownership has changed, update our ownership to reflect this - ERC721._transfer(curOwner, owner_, tokenId); - } - } - } - - /** - @dev See {IPatchworkPatch-unpatchedOwnerOf} - */ - function unpatchedOwnerOf(uint256 tokenId) public virtual view returns (address) { - return super.ownerOf(tokenId); - } - - /** - @dev always false because a patch cannot be locked as the ownership is inferred - @dev See {IPatchworkNFT-locked} - */ - function locked(uint256 /* tokenId */) public pure virtual override returns (bool) { - return false; - } - - /** - @dev always reverts because a patch cannot be locked as the ownership is inferred - @dev See {IPatchworkNFT-setLocked} - */ - function setLocked(uint256 /* tokenId */, bool /* locked_ */) public view virtual override { - revert PatchworkProtocol.CannotLockSoulboundPatch(address(this)); - } - - /** - @dev See {ERC721-_burn} - */ - function _burn(uint256 /*tokenId*/) internal virtual override { - revert PatchworkProtocol.UnsupportedOperation(); - } - - /** - @dev See {IPatchworkPatch-patchworkCompatible_} - */ - function patchworkCompatible_() external pure returns (bytes1) {} -} - -/** -@title PatchworkFragment -@dev base implementation of a Fragment is IPatchworkAssignableNFT -*/ -abstract contract PatchworkFragment is PatchworkNFT, IPatchworkAssignableNFT { - - /// Represents an assignment of a token from an external NFT contract to a token in this contract. - struct Assignment { - address tokenAddr; /// The address of the external NFT contract. - uint256 tokenId; /// The ID of the token in the external NFT contract. - } - - /// A mapping from token IDs in this contract to their assignments. - mapping(uint256 => Assignment) internal _assignments; - - /** - @dev See {IPatchworkNFT-getScopeName} - */ - function getScopeName() public view virtual override (IPatchworkAssignableNFT, PatchworkNFT) returns (string memory) { - return _scopeName; - } - - /** - @dev See {IERC165-supportsInterface} - */ - function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { - return interfaceID == type(IPatchworkAssignableNFT).interfaceId || - super.supportsInterface(interfaceID); - } - - /** - @dev See {IPatchworkAssignableNFT-assign} - */ - function assign(uint256 ourTokenId, address to, uint256 tokenId) public virtual mustHaveTokenWriteAuth(tokenId) { - // One time use policy - Assignment storage a = _assignments[ourTokenId]; - if (a.tokenAddr != address(0)) { - revert PatchworkProtocol.FragmentAlreadyAssigned(address(this), ourTokenId); - } - a.tokenAddr = to; - a.tokenId = tokenId; - emit Locked(ourTokenId); - } - - /** - @dev See {IPatchworkAssignableNFT-unassign} - */ - function unassign(uint256 tokenId) public virtual mustHaveTokenWriteAuth(tokenId) { - updateOwnership(tokenId); - delete _assignments[tokenId]; - emit Unlocked(tokenId); - } - - /** - @dev See {IPatchworkAssignableNFT-updateOwnership} - */ - function updateOwnership(uint256 tokenId) public virtual { - Assignment storage assignment = _assignments[tokenId]; - if (assignment.tokenAddr != address(0)) { - address owner_ = ownerOf(tokenId); - address curOwner = super.ownerOf(tokenId); - if (owner_ != curOwner) { - // Parent ownership has changed, update our ownership to reflect this - ERC721._transfer(curOwner, owner_, tokenId); - } - } - } - - /** - @dev owned by the assignment's owner - @dev See {IERC721-ownerOf} - */ - function ownerOf(uint256 tokenId) public view virtual override(ERC721, IERC721) returns (address) { - // If assigned, it's owned by the assignment, otherwise normal owner - Assignment storage assignment = _assignments[tokenId]; - if (assignment.tokenAddr != address(0)) { - return IERC721(assignment.tokenAddr).ownerOf(assignment.tokenId); - } - return super.ownerOf(tokenId); - } - - /** - @dev See {IPatchworkAssignableNFT-unassignedOwnerOf} - */ - function unassignedOwnerOf(uint256 tokenId) public virtual view returns (address) { - return super.ownerOf(tokenId); - } - - /** - @dev See {IPatchworkAssignableNFT-getAssignedTo} - */ - function getAssignedTo(uint256 ourTokenId) public virtual view returns (address, uint256) { - Assignment storage a = _assignments[ourTokenId]; - return (a.tokenAddr, a.tokenId); - } - - /** - @dev See {IPatchworkAssignableNFT-onAssignedTransfer} - */ - function onAssignedTransfer(address from, address to, uint256 tokenId) public virtual { - require(msg.sender == _manager); - emit Transfer(from, to, tokenId); - } - - /** - @dev See {IPatchworkNFT-locked} - */ - function locked(uint256 tokenId) public view virtual override returns (bool) { - // Locked when assigned (implicit) or if explicitly locked - return _assignments[tokenId].tokenAddr != address(0) || super.locked(tokenId); - } - - /** - @dev See {IPatchworkNFT-setLocked} - */ - function setLocked(uint256 tokenId, bool locked_) public virtual override { - if (msg.sender != ownerOf(tokenId)) { - revert PatchworkProtocol.NotAuthorized(msg.sender); - } - require(_assignments[tokenId].tokenAddr == address(0), "cannot setLocked assigned fragment"); - super.setLocked(tokenId, locked_); - } - - /** - @dev See {IPatchworkNFT-patchworkCompatible_} - */ - function patchworkCompatible_() external pure returns (bytes2) {} -} - -/** -@title PatchworkLiteRef -@dev base implementation of IPatchworkLiteRef -*/ -abstract contract PatchworkLiteRef is IPatchworkLiteRef, ERC165 { - - /// A mapping from reference IDs to their associated addresses. - mapping(uint8 => address) internal _referenceAddresses; - - /// A reverse mapping from addresses to their corresponding reference IDs. - mapping(address => uint8) internal _referenceAddressIds; - - /// A mapping indicating which reference IDs have been redacted. - mapping(uint8 => bool) internal _redactedReferenceIds; - - /// The ID that will be used for the next reference added. - uint8 internal _nextReferenceId; - - /** - @dev Constructor for the PatchworkLiteRef contract. Initializes the next reference ID to 1 to differentiate unregistered references. - */ - constructor() { - _nextReferenceId = 1; // Start at 1 so we can identify if we already have one registered - } - - /** - @notice implements a permission check for functions of this abstract class to use - @return allow true if write is allowed, false if not - */ - function _checkWriteAuth() internal virtual returns (bool allow); - - /** - @dev See {IERC165-supportsInterface} - */ - function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { - return interfaceID == type(IPatchworkLiteRef).interfaceId || - ERC165.supportsInterface(interfaceID); - } - - /** - @dev See {IPatchworkLiteRef-registerReferenceAddress} - */ - function registerReferenceAddress(address addr) public virtual _mustHaveWriteAuth returns (uint8 id) { - uint8 refId = _nextReferenceId; - if (_nextReferenceId == 255) { - revert PatchworkProtocol.OutOfIDs(); - } - _nextReferenceId++; - if (_referenceAddressIds[addr] != 0) { - revert PatchworkProtocol.FragmentAlreadyRegistered(addr); - } - _referenceAddresses[refId] = addr; - _referenceAddressIds[addr] = refId; - emit Register(address(this), addr, refId); - return refId; - } - - /** - @dev See {IPatchworkLiteRef-getReferenceId} - */ - function getReferenceId(address addr) public virtual returns (uint8 id, bool redacted) { - uint8 refId = _referenceAddressIds[addr]; - return (refId, _redactedReferenceIds[refId]); - } - - /** - @dev See {IPatchworkLiteRef-getReferenceAddress} - */ - function getReferenceAddress(uint8 id) public virtual returns (address addr, bool redacted) { - return (_referenceAddresses[id], _redactedReferenceIds[id]); - } - - /** - @dev See {IPatchworkLiteRef-redactReferenceAddress} - */ - function redactReferenceAddress(uint8 id) public virtual _mustHaveWriteAuth { - _redactedReferenceIds[id] = true; - emit Redact(address(this), _referenceAddresses[id]); - } - - /** - @dev See {IPatchworkLiteRef-unredactReferenceAddress} - */ - function unredactReferenceAddress(uint8 id) public virtual _mustHaveWriteAuth { - _redactedReferenceIds[id] = false; - emit Unredact(address(this), _referenceAddresses[id]); - } - - /** - @dev See {IPatchworkLiteRef-getLiteReference} - */ - function getLiteReference(address addr, uint256 tokenId) public virtual view returns (uint64 referenceAddress, bool redacted) { - uint8 refId = _referenceAddressIds[addr]; - if (refId == 0) { - return (0, false); - } - if (tokenId > type(uint56).max) { - revert PatchworkProtocol.UnsupportedTokenId(tokenId); - } - return (uint64(uint256(refId) << 56 | tokenId), _redactedReferenceIds[refId]); - } - - /** - @dev See {IPatchworkLiteRef-getReferenceAddressAndTokenId} - */ - function getReferenceAddressAndTokenId(uint64 referenceAddress) public virtual view returns (address addr, uint256 tokenId) { - // <8 bits of refId, 56 bits of tokenId> - uint8 refId = uint8(referenceAddress >> 56); - tokenId = referenceAddress & 0x00FFFFFFFFFFFFFF; // 64 bit mask - return (_referenceAddresses[refId], tokenId); - } - - modifier _mustHaveWriteAuth { - if (!_checkWriteAuth()) { - revert PatchworkProtocol.NotAuthorized(msg.sender); - } - _; - } -} \ No newline at end of file diff --git a/src/PatchworkNFTInterface.sol b/src/PatchworkNFTInterface.sol deleted file mode 100644 index 156e188..0000000 --- a/src/PatchworkNFTInterface.sol +++ /dev/null @@ -1,399 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "./IERC5192.sol"; - -/** -@title Patchwork Protocol NFT Interface Metadata -@author Runic Labs, Inc -@notice Metadata for IPatchworkNFT and related contract interfaces -*/ -interface PatchworkNFTInterfaceMeta { - /** - @notice Enumeration of possible field data types. - @dev This defines the various basic data types for the fields. - */ - enum FieldType { - BOOLEAN, ///< A Boolean type (true or false). - INT8, ///< An 8-bit signed integer. - INT16, ///< A 16-bit signed integer. - INT32, ///< A 32-bit signed integer. - INT64, ///< A 64-bit signed integer. - INT128, ///< A 128-bit signed integer. - INT256, ///< A 256-bit signed integer. - UINT8, ///< An 8-bit unsigned integer. - UINT16, ///< A 16-bit unsigned integer. - UINT32, ///< A 32-bit unsigned integer. - UINT64, ///< A 64-bit unsigned integer. - UINT128, ///< A 128-bit unsigned integer. - UINT256, ///< A 256-bit unsigned integer. - CHAR8, ///< An 8-character string. - CHAR16, ///< A 16-character string. - CHAR32, ///< A 32-character string. - CHAR64, ///< A 64-character string. - LITEREF ///< A Literef reference to a patchwork fragment - } - - /** - @notice Struct defining the metadata schema. - @dev This defines the overall structure of the metadata and contains entries describing each data field. - */ - struct MetadataSchema { - uint256 version; ///< Version of the metadata schema. - MetadataSchemaEntry[] entries; ///< Array of entries in the schema. - } - - /** - @notice Struct defining individual entries within the metadata schema. - @dev Represents each data field in the schema, detailing its properties and type. - */ - struct MetadataSchemaEntry { - uint256 id; ///< Index or unique identifier of the entry. - uint256 permissionId; ///< Permission identifier associated with the entry. - FieldType fieldType; ///< Type of field data (from the FieldType enum). - uint256 arrayLength; ///< Length of array for the field (0 means it's a single field). - FieldVisibility visibility; ///< Visibility level of the field. - uint256 slot; ///< Starting storage slot, may span multiple slots based on width. - uint256 offset; ///< Offset in bits within the storage slot. - string key; ///< Key or name associated with the field. - } - - /** - @notice Enumeration of field visibility options. - @dev Specifies whether a field is publicly accessible or private. - */ - enum FieldVisibility { - PUBLIC, ///< Field is publicly accessible. - PRIVATE ///< Field is private - } -} - -/** -@title Patchwork Protocol NFT Interface -@author Runic Labs, Inc -@notice Interface for contracts supporting Patchwork metadata standard -*/ -interface IPatchworkNFT is PatchworkNFTInterfaceMeta, IERC5192 { - /** - @notice Emitted when the freeze status is changed to frozen. - @param tokenId The identifier for a token. - */ - event Frozen(uint256 indexed tokenId); - - /** - @notice Emitted when the locking status is changed to not frozen. - @param tokenId The identifier for a token. - */ - event Thawed(uint256 indexed tokenId); - - /** - @notice Emitted when the permissions are changed for an NFT - @param to The address the permissions are assigned to - @param permissions The permissions - */ - event PermissionChange(address indexed to, uint256 permissions); - - /** - @notice Emitted when the schema has changed for an NFT - @param addr the address of the NFT - */ - event SchemaChange(address indexed addr); - - /** - @notice Get the scope this NFT claims to belong to - @return string the name of the scope - */ - function getScopeName() external returns (string memory); - - /** - @notice Returns the URI of the schema - @return string the URI of the schema - */ - function schemaURI() external returns (string memory); - - /** - @notice Returns the metadata schema - @return MetadataSchema the metadata schema - */ - function schema() external returns (MetadataSchema memory); - - /** - @notice Returns the URI of the image associated with the given token ID - @param tokenId ID of the token - @return string the image URI - */ - function imageURI(uint256 tokenId) external returns (string memory); - - /** - @notice Sets permissions for a given address - @param to Address to set permissions for - @param permissions Permissions value - */ - function setPermissions(address to, uint256 permissions) external; - - /** - @notice Stores packed metadata for a given token ID and slot - @param tokenId ID of the token - @param slot Slot to store metadata - @param data Metadata to store - */ - function storePackedMetadataSlot(uint256 tokenId, uint256 slot, uint256 data) external; - - /** - @notice Loads packed metadata for a given token ID and slot - @param tokenId ID of the token - @param slot Slot to load metadata from - @return uint256 the raw slot data as a uint256 - */ - function loadPackedMetadataSlot(uint256 tokenId, uint256 slot) external returns (uint256); - - /** - @notice Returns the freeze nonce for a given token ID - @param tokenId ID of the token - @return nonce the nonce - */ - function getFreezeNonce(uint256 tokenId) external returns (uint256 nonce); - - /** - @notice Sets the freeze status of a token - @param tokenId ID of the token - @param frozen Freeze status to set - */ - function setFrozen(uint256 tokenId, bool frozen) external; - - /** - @notice Gets the freeze status of a token (ERC-5192) - @param tokenId ID of the token - @return bool true if frozen, false if not - */ - function frozen(uint256 tokenId) external view returns (bool); - - /** - @notice Sets the lock status of a token - @param tokenId ID of the token - @param locked Lock status to set - */ - function setLocked(uint256 tokenId, bool locked) external; -} - -/** -@title Patchwork Protocol Patch Interface -@author Runic Labs, Inc -@notice Interface for contracts supporting Patchwork patch standard -*/ -interface IPatchworkPatch { - /** - @notice Get the scope this NFT claims to belong to - @return string the name of the scope - */ - function getScopeName() external returns (string memory); - - /** - @notice Creates a new token for the owner, representing a patch - @param owner Address of the owner of the token - @param originalNFTAddress Address of the original NFT - @param originalNFTTokenId ID of the original NFT token - @return tokenId ID of the newly minted token - */ - function mintPatch(address owner, address originalNFTAddress, uint256 originalNFTTokenId) external returns (uint256 tokenId); - - /** - @notice Updates the real underlying ownership of a token in storage (if different from current) - @param tokenId ID of the token - */ - function updateOwnership(uint256 tokenId) external; - - /** - @notice Returns the underlying stored owner of a token ignoring real patched NFT ownership - @param tokenId ID of the token - @return address Address of the owner - */ - function unpatchedOwnerOf(uint256 tokenId) external returns (address); - - /** - @notice A deliberately incompatible function to block implementing both assignable and patch - @return bytes1 Always returns 0x00 - */ - function patchworkCompatible_() external pure returns (bytes1); -} - -/** -@title Patchwork Protocol Assignable NFT Interface -@author Runic Labs, Inc -@notice Interface for contracts supporting Patchwork assignment -*/ -interface IPatchworkAssignableNFT { - /** - @notice Get the scope this NFT claims to belong to - @return string the name of the scope - */ - function getScopeName() external returns (string memory); - - /** - @notice Assigns a token to another - @param ourTokenId ID of our token - @param to Address to assign to - @param tokenId ID of the token to assign - */ - function assign(uint256 ourTokenId, address to, uint256 tokenId) external; - - /** - @notice Unassigns a token - @param ourTokenId ID of our token - */ - function unassign(uint256 ourTokenId) external; - - /** - @notice Returns the address and token ID that our token is assigned to - @param ourTokenId ID of our token - @return address the address this is assigned to - @return uint256 the tokenId this is assigned to - */ - function getAssignedTo(uint256 ourTokenId) external view returns (address, uint256); - - /** - @notice Returns the underlying stored owner of a token ignoring current assignment - @param ourTokenId ID of our token - @return address address of the owner - */ - function unassignedOwnerOf(uint256 ourTokenId) external view returns (address); - - /** - @notice Sends events for a token when the assigned-to token has been transferred - @param from Sender address - @param to Recipient address - @param tokenId ID of the token - */ - function onAssignedTransfer(address from, address to, uint256 tokenId) external; - - /** - @notice Updates the real underlying ownership of a token in storage (if different from current) - @param tokenId ID of the token - */ - function updateOwnership(uint256 tokenId) external; - - /** - @notice A deliberately incompatible function to block implementing both assignable and patch - @return bytes2 Always returns 0x0000 - */ - function patchworkCompatible_() external pure returns (bytes2); -} - -/** -@title Patchwork Protocol LiteRef NFT Interface -@author Runic Labs, Inc -@notice Interface for contracts that have Lite Reference ID support -*/ -interface IPatchworkLiteRef { - /** - @notice Emitted when a contract redacts a fragment - @param target the contract which issued the redaction - @param fragment the fragment that was redacted - */ - event Redact(address indexed target, address indexed fragment); - - /** - @notice Emitted when a contract unredacts a fragment - @param target the contract which revoked the redaction - @param fragment the fragment that was unredacted - */ - event Unredact(address indexed target, address indexed fragment); - - /** - @notice Emitted when a contract registers a fragment - @param target the contract that registered the fragment - @param fragment the fragment that was registered - @param idx the idx of the literef - */ - event Register(address indexed target, address indexed fragment, uint8 idx); - - /** - @notice Registers a reference address - @param addr Address to register - @return id ID assigned to the address - */ - function registerReferenceAddress(address addr) external returns (uint8 id); - - /** - @notice Gets the ID assigned to the address from registration - @param addr Registered address - @return id ID assigned to the address - @return redacted Redacted status - */ - function getReferenceId(address addr) external returns (uint8 id, bool redacted); - - /** - @notice Gets the address assigned to this id - @param id ID assigned to the address - @return addr Registered address - @return redacted Redacted status - */ - function getReferenceAddress(uint8 id) external returns (address addr, bool redacted); - - /** - @notice Redacts a reference address - @param id ID of the address to redact - */ - function redactReferenceAddress(uint8 id) external; - - /** - @notice Unredacts a reference address - @param id ID of the address to unredact - */ - function unredactReferenceAddress(uint8 id) external; - - /** - @notice Returns a lite reference for a given address and token ID - @param addr Address to get reference for - @param tokenId ID of the token - @return liteRef Lite reference - @return redacted Redacted status - */ - function getLiteReference(address addr, uint256 tokenId) external view returns (uint64 liteRef, bool redacted); - - /** - @notice Returns an address and token ID for a given lite reference - @param liteRef Lite reference to get address and token ID for - @return addr Address - @return tokenId Token ID - */ - function getReferenceAddressAndTokenId(uint64 liteRef) external view returns (address addr, uint256 tokenId); - - /** - @notice Adds a reference to a token - @param tokenId ID of the token - @param referenceAddress Reference address to add - */ - function addReference(uint256 tokenId, uint64 referenceAddress) external; - - /** - @notice Adds multiple references to a token - @param tokenId ID of the token - @param liteRefs Array of lite references to add - */ - function batchAddReferences(uint256 tokenId, uint64[] calldata liteRefs) external; - - /** - @notice Removes a reference from a token - @param tokenId ID of the token - @param liteRef Lite reference to remove - */ - function removeReference(uint256 tokenId, uint64 liteRef) external; - - /** - @notice Loads a reference address and token ID at a given index - @param idx Index to load from - @return addr Address - @return tokenId Token ID - */ - function loadReferenceAddressAndTokenId(uint256 idx) external view returns (address addr, uint256 tokenId); - - /** - @notice Loads all references for a given token ID - @param tokenId ID of the token - @return addresses Array of addresses - @return tokenIds Array of token IDs - */ - function loadAllReferences(uint256 tokenId) external view returns (address[] memory addresses, uint256[] memory tokenIds); -} diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol new file mode 100644 index 0000000..e61d76f --- /dev/null +++ b/src/PatchworkPatch.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "./PatchworkNFT.sol"; +import "./IPatchworkPatch.sol"; + +/** +@title PatchworkPatch +@dev Base implementation of IPatchworkPatch +@dev It is soul-bound to another ERC-721 and cannot be transferred or reassigned. +@dev It extends the functionalities of PatchworkNFT and implements the IPatchworkPatch interface. +*/ +abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { + + /// @dev Mapping from token ID to the address of the NFT that this patch is applied to. + mapping(uint256 => address) internal _patchedAddresses; + + /// @dev Mapping from token ID to the token ID of the NFT that this patch is applied to. + mapping(uint256 => uint256) internal _patchedTokenIds; + + /** + @dev See {IERC165-supportsInterface} + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(IPatchworkPatch).interfaceId || + super.supportsInterface(interfaceID); + } + + /** + @dev See {IPatchworkNFT-getScopeName} + */ + function getScopeName() public view virtual override(PatchworkNFT, IPatchworkPatch) returns (string memory) { + return _scopeName; + } + + /** + @dev will return the current owner of the patched address+tokenId + @dev See {IERC721-ownerOf} + */ + function ownerOf(uint256 tokenId) public view virtual override(ERC721, IERC721) returns (address) { + return IERC721(_patchedAddresses[tokenId]).ownerOf(_patchedTokenIds[tokenId]); + } + + /** + @notice stores a patch + @param tokenId the tokenId of the patch + @param originalNFTAddress the address of the original ERC-721 we are patching + @param originalNFTTokenId the tokenId of the original ERC-721 we are patching + */ + function _storePatch(uint256 tokenId, address originalNFTAddress, uint256 originalNFTTokenId) internal virtual { + _patchedAddresses[tokenId] = originalNFTAddress; + _patchedTokenIds[tokenId] = originalNFTTokenId; + } + + /** + @dev See {IPatchworkPatch-updateOwnership} + */ + function updateOwnership(uint256 tokenId) public virtual { + address patchedAddr = _patchedAddresses[tokenId]; + if (patchedAddr != address(0)) { + address owner_ = ownerOf(tokenId); + address curOwner = super.ownerOf(tokenId); + if (owner_ != curOwner) { + // Parent ownership has changed, update our ownership to reflect this + ERC721._transfer(curOwner, owner_, tokenId); + } + } + } + + /** + @dev See {IPatchworkPatch-unpatchedOwnerOf} + */ + function unpatchedOwnerOf(uint256 tokenId) public virtual view returns (address) { + return super.ownerOf(tokenId); + } + + /** + @dev always false because a patch cannot be locked as the ownership is inferred + @dev See {IPatchworkNFT-locked} + */ + function locked(uint256 /* tokenId */) public pure virtual override returns (bool) { + return false; + } + + /** + @dev always reverts because a patch cannot be locked as the ownership is inferred + @dev See {IPatchworkNFT-setLocked} + */ + function setLocked(uint256 /* tokenId */, bool /* locked_ */) public view virtual override { + revert IPatchworkProtocol.CannotLockSoulboundPatch(address(this)); + } + + /** + @dev See {ERC721-_burn} + */ + function _burn(uint256 /*tokenId*/) internal virtual override { + revert IPatchworkProtocol.UnsupportedOperation(); + } + + /** + @dev See {IPatchworkPatch-patchworkCompatible_} + */ + function patchworkCompatible_() external pure returns (bytes1) {} +} \ No newline at end of file diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index e2daff0..e1449a8 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -1,392 +1,26 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./PatchworkNFTInterface.sol"; +import "./IPatchworkNFT.sol"; +import "./IPatchworkAssignableNFT.sol"; +import "./IPatchworkLiteRef.sol"; +import "./IPatchworkPatch.sol"; import "./IPatchworkAccountPatch.sol"; +import "./IPatchworkProtocol.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; /** @title Patchwork Protocol @author Runic Labs, Inc @notice Manages data integrity of relational NFTs implemented with Patchwork interfaces */ -contract PatchworkProtocol { - - /** - @notice The address is not authorized to perform this action - @param addr The address attempting to perform the action - */ - error NotAuthorized(address addr); - - /** - @notice The scope with the provided name already exists - @param scopeName Name of the scope - */ - error ScopeExists(string scopeName); - - /** - @notice The scope with the provided name does not exist - @param scopeName Name of the scope - */ - error ScopeDoesNotExist(string scopeName); - - /** - @notice Transfer of the scope to the provided address is not allowed - @param to Address not allowed for scope transfer - */ - error ScopeTransferNotAllowed(address to); - - /** - @notice The token with the provided ID at the given address is frozen - @param addr Address of the token owner - @param tokenId ID of the frozen token - */ - error Frozen(address addr, uint256 tokenId); - - /** - @notice The token with the provided ID at the given address is locked - @param addr Address of the token owner - @param tokenId ID of the locked token - */ - error Locked(address addr, uint256 tokenId); - - /** - @notice The address is not whitelisted for the given scope - @param scopeName Name of the scope - @param addr Address that isn't whitelisted - */ - error NotWhitelisted(string scopeName, address addr); - - /** - @notice The address at the given address has already been patched - @param addr The address that was patched - @param patchAddress Address of the patch applied - */ - error AccountAlreadyPatched(address addr, address patchAddress); - - /** - @notice The token at the given address has already been patched - @param addr Address of the token owner - @param tokenId ID of the patched token - @param patchAddress Address of the patch applied - */ - error AlreadyPatched(address addr, uint256 tokenId, address patchAddress); - - /** - @notice The provided input lengths are not compatible or valid - @dev for any multi array inputs, they must be the same length - */ - error BadInputLengths(); - - /** - @notice The fragment at the given address is unregistered - @param addr Address of the unregistered fragment - */ - error FragmentUnregistered(address addr); - - /** - @notice The fragment at the given address has been redacted - @param addr Address of the redacted fragment - */ - error FragmentRedacted(address addr); - - /** - @notice The fragment with the provided ID at the given address is already assigned - @param addr Address of the fragment - @param tokenId ID of the assigned fragment - */ - error FragmentAlreadyAssigned(address addr, uint256 tokenId); - - /** - @notice The fragment with the provided ID at the given address is already assigned in the scope - @param scopeName Name of the scope - @param addr Address of the fragment - @param tokenId ID of the fragment - */ - error FragmentAlreadyAssignedInScope(string scopeName, address addr, uint256 tokenId); - - /** - @notice The reference was not found in the scope for the given fragment and target - @param scopeName Name of the scope - @param target Address of the target token - @param fragment Address of the fragment - @param tokenId ID of the fragment - */ - error RefNotFoundInScope(string scopeName, address target, address fragment, uint256 tokenId); - - /** - @notice The fragment with the provided ID at the given address is not assigned - @param addr Address of the fragment - @param tokenId ID of the fragment - */ - error FragmentNotAssigned(address addr, uint256 tokenId); - - /** - @notice The fragment at the given address is already registered - @param addr Address of the registered fragment - */ - error FragmentAlreadyRegistered(address addr); - - /** - @notice Ran out of available IDs for allocation - @dev Max 255 IDs per NFT - */ - error OutOfIDs(); - - /** - @notice The provided token ID is unsupported - @dev TokenIds may only be 56 bits long - @param tokenId The unsupported token ID - */ - error UnsupportedTokenId(uint256 tokenId); - - /** - @notice Cannot lock the soulbound patch at the given address - @param addr Address of the soulbound patch - */ - error CannotLockSoulboundPatch(address addr); - - /** - @notice The token with the provided ID at the given address is not frozen - @param addr Address of the token owner - @param tokenId ID of the token - */ - error NotFrozen(address addr, uint256 tokenId); - - /** - @notice The nonce for the token with the provided ID at the given address is incorrect - @dev It may be incorrect or a newer nonce may be present - @param addr Address of the token owner - @param tokenId ID of the token - @param nonce The incorrect nonce - */ - error IncorrectNonce(address addr, uint256 tokenId, uint256 nonce); - - /** - @notice Self assignment of the token with the provided ID at the given address is not allowed - @param addr Address of the token owner - @param tokenId ID of the token - */ - error SelfAssignmentNotAllowed(address addr, uint256 tokenId); - - /** - @notice Transfer of the token with the provided ID at the given address is not allowed - @param addr Address of the token owner - @param tokenId ID of the token - */ - error TransferNotAllowed(address addr, uint256 tokenId); - - /** - @notice Transfer of the token with the provided ID at the given address is blocked by an assignment - @param addr Address of the token owner - @param tokenId ID of the token - */ - error TransferBlockedByAssignment(address addr, uint256 tokenId); - - /** - @notice A rule is blocking the mint to this owner address - @param addr Address of the token owner - */ - error MintNotAllowed(address addr); - - /** - @notice The token at the given address is not IPatchworkAssignable - @param addr Address of the non-assignable token - */ - error NotPatchworkAssignable(address addr); - - /** - @notice A data integrity error has been detected - @dev Addr+TokenId is expected where addr2+tokenId2 is present - @param addr Address of the first token - @param tokenId ID of the first token - @param addr2 Address of the second token - @param tokenId2 ID of the second token - */ - error DataIntegrityError(address addr, uint256 tokenId, address addr2, uint256 tokenId2); - - /** - @notice The operation is not supported - */ - error UnsupportedOperation(); - - /** - @notice Represents a defined scope within the system - @dev Contains details about the scope ownership, permissions, and mappings for references and assignments - */ - struct Scope { - /** - @notice Owner of this scope - @dev Address of the account or contract that owns this scope - */ - address owner; - - /** - @notice Owner-elect - @dev Used in two-step transfer process. If this is set, only this owner can accept the transfer - */ - address ownerElect; - - /** - @notice Indicates whether a user is allowed to patch within this scope - @dev True if a user can patch, false otherwise. If false, only operators and the scope owner can perform patching. - */ - bool allowUserPatch; - - /** - @notice Indicates whether a user is allowed to assign within this scope - @dev True if a user can assign, false otherwise. If false, only operators and the scope owner can perform assignments. - */ - bool allowUserAssign; - - /** - @notice Indicates if a whitelist is required for operations within this scope - @dev True if whitelist is required, false otherwise - */ - bool requireWhitelist; - - /** - @notice Mapped list of operator addresses for this scope - @dev Address of the operator mapped to a boolean indicating if they are an operator - */ - mapping(address => bool) operators; - - /** - @notice Mapped list of lightweight references within this scope - @dev A hash of liteRefAddr + reference provides uniqueness - */ - mapping(bytes32 => bool) liteRefs; - - /** - @notice Mapped whitelist of addresses that belong to this scope - @dev Address mapped to a boolean indicating if it's whitelisted - */ - mapping(address => bool) whitelist; - - /** - @notice Mapped list of unique patches associated with this scope - @dev Hash of the patch mapped to a boolean indicating its uniqueness - */ - mapping(bytes32 => bool) uniquePatches; - } +contract PatchworkProtocol is IPatchworkProtocol { + /// Scopes mapping(string => Scope) private _scopes; /** - @notice Emitted when a fragment is assigned - @param owner The owner of the target and fragment - @param fragmentAddress The address of the fragment's contract - @param fragmentTokenId The tokenId of the fragment - @param targetAddress The address of the target's contract - @param targetTokenId The tokenId of the target - */ - event Assign(address indexed owner, address fragmentAddress, uint256 fragmentTokenId, address indexed targetAddress, uint256 indexed targetTokenId); - - /** - @notice Emitted when a fragment is unassigned - @param owner The owner of the fragment - @param fragmentAddress The address of the fragment's contract - @param fragmentTokenId The tokenId of the fragment - @param targetAddress The address of the target's contract - @param targetTokenId The tokenId of the target - */ - event Unassign(address indexed owner, address fragmentAddress, uint256 fragmentTokenId, address indexed targetAddress, uint256 indexed targetTokenId); - - /** - @notice Emitted when a patch is minted - @param owner The owner of the patch - @param originalAddress The address of the original NFT's contract - @param originalTokenId The tokenId of the original NFT - @param patchAddress The address of the patch's contract - @param patchTokenId The tokenId of the patch - */ - event Patch(address indexed owner, address originalAddress, uint256 originalTokenId, address indexed patchAddress, uint256 indexed patchTokenId); - - /** - @notice Emitted when an account patch is minted - @param owner The owner of the patch - @param originalAddress The address of the original NFT's contract - @param patchAddress The address of the patch's contract - @param patchTokenId The tokenId of the patch - */ - event AccountPatch(address indexed owner, address originalAddress, address indexed patchAddress, uint256 indexed patchTokenId); - - /** - @notice Emitted when a new scope is claimed - @param scopeName The name of the claimed scope - @param owner The owner of the scope - */ - event ScopeClaim(string indexed scopeName, address indexed owner); - - /** - @notice Emitted when a scope has elected a new owner to transfer to - @param scopeName The name of the transferred scope - @param from The owner of the scope - @param to The owner-elect of the scope - */ - event ScopeTransferElect(string indexed scopeName, address indexed from, address indexed to); - - /** - @notice Emitted when a scope transfer is canceled - @param scopeName The name of the transferred scope - @param from The owner of the scope - @param to The owner-elect of the scope - */ - event ScopeTransferCancel(string indexed scopeName, address indexed from, address indexed to); - - /** - @notice Emitted when a scope is transferred - @param scopeName The name of the transferred scope - @param from The address transferring the scope - @param to The recipient of the scope - */ - event ScopeTransfer(string indexed scopeName, address indexed from, address indexed to); - - /** - @notice Emitted when a scope has an operator added - @param scopeName The name of the scope - @param actor The address responsible for the action - @param operator The new operator's address - */ - event ScopeAddOperator(string indexed scopeName, address indexed actor, address indexed operator); - - /** - @notice Emitted when a scope has an operator removed - @param scopeName The name of the scope - @param actor The address responsible for the action - @param operator The operator's address being removed - */ - event ScopeRemoveOperator(string indexed scopeName, address indexed actor, address indexed operator); - - /** - @notice Emitted when a scope's rules are changed - @param scopeName The name of the scope - @param actor The address responsible for the action - @param allowUserPatch Indicates whether user patches are allowed - @param allowUserAssign Indicates whether user assignments are allowed - @param requireWhitelist Indicates whether a whitelist is required - */ - event ScopeRuleChange(string indexed scopeName, address indexed actor, bool allowUserPatch, bool allowUserAssign, bool requireWhitelist); - - /** - @notice Emitted when a scope has an address added to the whitelist - @param scopeName The name of the scope - @param actor The address responsible for the action - @param addr The address being added to the whitelist - */ - event ScopeWhitelistAdd(string indexed scopeName, address indexed actor, address indexed addr); - - /** - @notice Emitted when a scope has an address removed from the whitelist - @param scopeName The name of the scope - @param actor The address responsible for the action - @param addr The address being removed from the whitelist - */ - event ScopeWhitelistRemove(string indexed scopeName, address indexed actor, address indexed addr); - - /** - @notice Claim a scope - @param scopeName the name of the scope + @dev See {IPatchworkProtocol-claimScope} */ function claimScope(string calldata scopeName) public { Scope storage s = _scopes[scopeName]; @@ -399,10 +33,7 @@ contract PatchworkProtocol { } /** - @notice Transfer ownership of a scope - @dev must be accepted by transferee - see {acceptScopeTransfer} - @param scopeName Name of the scope - @param newOwner Address of the new owner + @dev See {IPatchworkProtocol-transferScopeOwnership} */ function transferScopeOwnership(string calldata scopeName, address newOwner) public { Scope storage s = _mustHaveScope(scopeName); @@ -415,8 +46,7 @@ contract PatchworkProtocol { } /** - @notice Cancel a pending scope transfer - @param scopeName Name of the scope + @dev See {IPatchworkProtocol-cancelScopeTransfer} */ function cancelScopeTransfer(string calldata scopeName) public { Scope storage s = _mustHaveScope(scopeName); @@ -426,8 +56,7 @@ contract PatchworkProtocol { } /** - @notice Accept a scope transfer - @param scopeName Name of the scope + @dev See {IPatchworkProtocol-acceptScopeTransfer} */ function acceptScopeTransfer(string calldata scopeName) public { Scope storage s = _mustHaveScope(scopeName); @@ -442,27 +71,21 @@ contract PatchworkProtocol { } /** - @notice Get owner-elect of a scope - @param scopeName Name of the scope - @return ownerElect Address of the scope's owner-elect + @dev See {IPatchworkProtocol-getScopeOwnerElect} */ function getScopeOwnerElect(string calldata scopeName) public view returns (address ownerElect) { return _scopes[scopeName].ownerElect; } /** - @notice Get owner of a scope - @param scopeName Name of the scope - @return owner Address of the scope owner + @dev See {IPatchworkProtocol-getScopeOwner} */ function getScopeOwner(string calldata scopeName) public view returns (address owner) { return _scopes[scopeName].owner; } /** - @notice Add an operator to a scope - @param scopeName Name of the scope - @param op Address of the operator + @dev See {IPatchworkProtocol-addOperator} */ function addOperator(string calldata scopeName, address op) public { Scope storage s = _mustHaveScope(scopeName); @@ -472,9 +95,7 @@ contract PatchworkProtocol { } /** - @notice Remove an operator from a scope - @param scopeName Name of the scope - @param op Address of the operator + @dev See {IPatchworkProtocol-removeOperator} */ function removeOperator(string calldata scopeName, address op) public { Scope storage s = _mustHaveScope(scopeName); @@ -484,11 +105,7 @@ contract PatchworkProtocol { } /** - @notice Set rules for a scope - @param scopeName Name of the scope - @param allowUserPatch Boolean indicating whether user patches are allowed - @param allowUserAssign Boolean indicating whether user assignments are allowed - @param requireWhitelist Boolean indicating whether whitelist is required + @dev See {IPatchworkProtocol-setScopeRules} */ function setScopeRules(string calldata scopeName, bool allowUserPatch, bool allowUserAssign, bool requireWhitelist) public { Scope storage s = _mustHaveScope(scopeName); @@ -500,9 +117,7 @@ contract PatchworkProtocol { } /** - @notice Add an address to a scope's whitelist - @param scopeName Name of the scope - @param addr Address to be whitelisted + @dev See {IPatchworkProtocol-addWhitelist} */ function addWhitelist(string calldata scopeName, address addr) public { Scope storage s = _mustHaveScope(scopeName); @@ -512,9 +127,7 @@ contract PatchworkProtocol { } /** - @notice Remove an address from a scope's whitelist - @param scopeName Name of the scope - @param addr Address to be removed from the whitelist + @dev See {IPatchworkProtocol-removeWhitelist} */ function removeWhitelist(string calldata scopeName, address addr) public { Scope storage s = _mustHaveScope(scopeName); @@ -524,11 +137,7 @@ contract PatchworkProtocol { } /** - @notice Create a new patch - @param originalNFTAddress Address of the original NFT - @param originalNFTTokenId Token ID of the original NFT - @param patchAddress Address of the IPatchworkPatch to mint - @return tokenId Token ID of the newly created patch + @dev See {IPatchworkProtocol-createAccountPatch} */ function createPatch(address originalNFTAddress, uint originalNFTTokenId, address patchAddress) public returns (uint256 tokenId) { IPatchworkPatch patch = IPatchworkPatch(patchAddress); @@ -556,10 +165,7 @@ contract PatchworkProtocol { } /** - @notice Create a new account patch - @param originalAddress Address of the original account - @param patchAddress Address of the IPatchworkPatch to mint - @return tokenId Token ID of the newly created patch + @dev See {IPatchworkProtocol-createAccountPatch} */ function createAccountPatch(address owner, address originalAddress, address patchAddress) public returns (uint256 tokenId) { IPatchworkAccountPatch patch = IPatchworkAccountPatch(patchAddress); @@ -586,11 +192,7 @@ contract PatchworkProtocol { } /** - @notice Assigns an NFT relation to have an IPatchworkLiteRef form a LiteRef to a IPatchworkAssignableNFT - @param fragment The IPatchworkAssignableNFT address to assign - @param fragmentTokenId The IPatchworkAssignableNFT Token ID to assign - @param target The IPatchworkLiteRef address to hold the reference to the fragment - @param targetTokenId The IPatchworkLiteRef Token ID to hold the reference to the fragment + @dev See {IPatchworkProtocol-assignNFT} */ function assignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { address targetOwner = IERC721(target).ownerOf(targetTokenId); @@ -600,11 +202,7 @@ contract PatchworkProtocol { } /** - @notice Assign multiple NFT fragments to a target NFT in batch - @param fragments The array of addresses of the fragment IPatchworkAssignableNFTs - @param tokenIds The array of token IDs of the fragment IPatchworkAssignableNFTs - @param target The address of the target IPatchworkLiteRef NFT - @param targetTokenId The token ID of the target IPatchworkLiteRef NFT + @dev See {IPatchworkProtocol-batchAssignNFT} */ function batchAssignNFT(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) public mustNotBeFrozen(target, targetTokenId) { if (fragments.length != tokenIds.length) { @@ -684,9 +282,7 @@ contract PatchworkProtocol { } /** - @notice Unassign a NFT fragment from a target NFT - @param fragment The IPatchworkAssignableNFT address of the fragment NFT - @param fragmentTokenId The IPatchworkAssignableNFT token ID of the fragment NFT + @dev See {IPatchworkProtocol-unassignNFT} */ function unassignNFT(address fragment, uint fragmentTokenId) public mustNotBeFrozen(fragment, fragmentTokenId) { IPatchworkAssignableNFT assignableNFT = IPatchworkAssignableNFT(fragment); @@ -722,10 +318,7 @@ contract PatchworkProtocol { } /** - @notice Apply transfer rules and actions of a specific token from one address to another - @param from The address of the sender - @param to The address of the receiver - @param tokenId The ID of the token to be transferred + @dev See {IPatchworkProtocol-applyTransfer} */ function applyTransfer(address from, address to, uint256 tokenId) public { address nft = msg.sender; @@ -778,10 +371,8 @@ contract PatchworkProtocol { } /** - @notice Update the ownership tree of a specific Patchwork NFT - @param nft The address of the Patchwork NFT - @param tokenId The ID of the token whose ownership tree needs to be updated - */ + @dev See {IPatchworkProtocol-updateOwnershipTree} + */ function updateOwnershipTree(address nft, uint256 tokenId) public { if (IERC165(nft).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { IPatchworkLiteRef liteRefNFT = IPatchworkLiteRef(nft); diff --git a/src/sampleNFTs/TestAccountPatchNFT.sol b/src/sampleNFTs/TestAccountPatchNFT.sol index 57f42f2..43278fb 100644 --- a/src/sampleNFTs/TestAccountPatchNFT.sol +++ b/src/sampleNFTs/TestAccountPatchNFT.sol @@ -32,11 +32,11 @@ contract TestAccountPatchNFT is PatchworkAccountPatch { function mintPatch(address to, address original) public returns (uint256) { if (msg.sender != _manager) { - revert PatchworkProtocol.NotAuthorized(msg.sender); + revert IPatchworkProtocol.NotAuthorized(msg.sender); } if (_sameOwnerModel) { if (to != original) { - revert PatchworkProtocol.MintNotAllowed(to); + revert IPatchworkProtocol.MintNotAllowed(to); } } uint256 tokenId = _nextTokenId; @@ -63,7 +63,7 @@ contract TestAccountPatchNFT is PatchworkAccountPatch { if (from == address(0)) { // mint allowed } else if (to != address(0)) { - revert PatchworkProtocol.TransferNotAllowed(address(this), firstTokenId); + revert IPatchworkProtocol.TransferNotAllowed(address(this), firstTokenId); } } } diff --git a/src/sampleNFTs/TestFragmentLiteRefNFT.sol b/src/sampleNFTs/TestFragmentLiteRefNFT.sol index 326cfa3..112d1ad 100644 --- a/src/sampleNFTs/TestFragmentLiteRefNFT.sol +++ b/src/sampleNFTs/TestFragmentLiteRefNFT.sol @@ -9,11 +9,8 @@ pragma solidity ^0.8.13; Has metadata as defined in totem-metadata.json */ -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "forge-std/console.sol"; -import "../PatchworkNFTInterface.sol"; -import "../PatchworkNFTBase.sol"; - +import "../PatchworkFragment.sol"; +import "../PatchworkLiteRef.sol"; enum FragmentType { BASE, diff --git a/src/sampleNFTs/TestPatchLiteRefNFT.sol b/src/sampleNFTs/TestPatchLiteRefNFT.sol index cd72d9c..4f2dd77 100644 --- a/src/sampleNFTs/TestPatchLiteRefNFT.sol +++ b/src/sampleNFTs/TestPatchLiteRefNFT.sol @@ -9,11 +9,8 @@ pragma solidity ^0.8.13; Has metadata as defined in totem-metadata.json */ -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "forge-std/console.sol"; -import "../PatchworkNFTInterface.sol"; -import "../PatchworkNFTBase.sol"; - +import "../PatchworkPatch.sol"; +import "../PatchworkLiteRef.sol"; struct TestPatchLiteRefNFTMetadata { uint64[8] artifactIDs; diff --git a/src/sampleNFTs/TestPatchworkNFT.sol b/src/sampleNFTs/TestPatchworkNFT.sol index 3be3677..44d8263 100644 --- a/src/sampleNFTs/TestPatchworkNFT.sol +++ b/src/sampleNFTs/TestPatchworkNFT.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "../PatchworkNFTInterface.sol"; -import "../PatchworkNFTBase.sol"; +import "../PatchworkNFT.sol"; contract TestPatchworkNFT is PatchworkNFT { diff --git a/test/PatchworkAccountPatch.t.sol b/test/PatchworkAccountPatch.t.sol index f6b5ecf..3d42bea 100644 --- a/test/PatchworkAccountPatch.t.sol +++ b/test/PatchworkAccountPatch.t.sol @@ -41,20 +41,20 @@ contract PatchworkAccountPatchTest is Test { vm.prank(_scopeOwner); TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); // User patching is off, not authorized - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, _defaultUser)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); vm.prank(_scopeOwner); uint256 tokenId = _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); assertEq(_userAddress, testAccountPatchNFT.ownerOf(tokenId)); // Duplicate should fail vm.prank(_scopeOwner); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.AccountAlreadyPatched.selector, _user2Address, address(testAccountPatchNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.AccountAlreadyPatched.selector, _user2Address, address(testAccountPatchNFT))); _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); // Test transfer vm.prank(_userAddress); testAccountPatchNFT.transferFrom(_userAddress, address(55), tokenId); vm.prank(address(55)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.UnsupportedOperation.selector)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedOperation.selector)); testAccountPatchNFT.burn(tokenId); } @@ -62,12 +62,12 @@ contract PatchworkAccountPatchTest is Test { // Same owner model, not transferrable vm.prank(_scopeOwner); TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), true); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.MintNotAllowed.selector, _userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.MintNotAllowed.selector, _userAddress)); vm.prank(_scopeOwner); _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); vm.prank(_scopeOwner); uint256 tokenId = _prot.createAccountPatch(_userAddress, _userAddress, address(testAccountPatchNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.TransferNotAllowed.selector, address(testAccountPatchNFT), tokenId)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TransferNotAllowed.selector, address(testAccountPatchNFT), tokenId)); vm.prank(_userAddress); testAccountPatchNFT.transferFrom(_userAddress, address(55), tokenId); } diff --git a/test/PatchworkNFTBase.t.sol b/test/PatchworkNFTBase.t.sol index df67177..625938a 100644 --- a/test/PatchworkNFTBase.t.sol +++ b/test/PatchworkNFTBase.t.sol @@ -60,7 +60,7 @@ contract PatchworkNFTBaseTest is Test { function testLoadStorePackedMetadataSlot() public { testPatchworkNFT.mint(userAddress, 1); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, defaultUser)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, defaultUser)); testPatchworkNFT.storePackedMetadataSlot(1, 0, 0x505050); vm.prank(scopeOwner); testPatchworkNFT.storePackedMetadataSlot(1, 0, 0x505050); @@ -122,7 +122,7 @@ contract PatchworkNFTBaseTest is Test { function testTransferFromWithFreezeNonce() public { // TODO make sure these are calling checkTransfer on proto testPatchworkNFT.mint(userAddress, 1); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, defaultUser)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, defaultUser)); testPatchworkNFT.setFrozen(1, true); vm.prank(userAddress); testPatchworkNFT.setFrozen(1, true); @@ -142,26 +142,26 @@ contract PatchworkNFTBaseTest is Test { testPatchworkNFT.setFrozen(1, false); assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotFrozen.selector, testPatchworkNFT, 1)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, testPatchworkNFT, 1)); testPatchworkNFT.transferFromWithFreezeNonce(user2Address, userAddress, 1, 1); assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotFrozen.selector, testPatchworkNFT, 1)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, testPatchworkNFT, 1)); testPatchworkNFT.safeTransferFromWithFreezeNonce(user2Address, userAddress, 1, 1); assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotFrozen.selector, testPatchworkNFT, 1)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, testPatchworkNFT, 1)); testPatchworkNFT.safeTransferFromWithFreezeNonce(user2Address, userAddress, 1, bytes("abcd"), 1); assertEq(user2Address, testPatchworkNFT.ownerOf(1)); // test incorrect nonce revert testPatchworkNFT.setFrozen(1, true); assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.IncorrectNonce.selector, testPatchworkNFT, 1, 0)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, testPatchworkNFT, 1, 0)); testPatchworkNFT.transferFromWithFreezeNonce(user2Address, userAddress, 1, 0); assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.IncorrectNonce.selector, testPatchworkNFT, 1, 0)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, testPatchworkNFT, 1, 0)); testPatchworkNFT.safeTransferFromWithFreezeNonce(user2Address, userAddress, 1, 0); assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.IncorrectNonce.selector, testPatchworkNFT, 1, 0)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, testPatchworkNFT, 1, 0)); testPatchworkNFT.safeTransferFromWithFreezeNonce(user2Address, userAddress, 1, bytes("abcd"), 0); assertEq(user2Address, testPatchworkNFT.ownerOf(1)); vm.stopPrank(); @@ -187,12 +187,12 @@ contract PatchworkNFTBaseTest is Test { uint256 patchTokenId = prot.createPatch(address(testBaseNFT), baseTokenId, address(testPatchLiteRefNFT)); bool locked = testPatchLiteRefNFT.locked(patchTokenId); assertFalse(locked); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.CannotLockSoulboundPatch.selector, testPatchLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.CannotLockSoulboundPatch.selector, testPatchLiteRefNFT)); testPatchLiteRefNFT.setLocked(patchTokenId, true); } function testReferenceAddresses() public { - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, defaultUser)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, defaultUser)); uint8 refIdx = testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); (uint64 ref, bool redacted) = testPatchLiteRefNFT.getLiteReference(address(testFragmentLiteRefNFT), 1); assertEq(0, ref); @@ -213,41 +213,41 @@ contract PatchworkNFTBaseTest is Test { uint256 baseTokenId = testBaseNFT.mint(userAddress); uint256 fragmentTokenId = testFragmentLiteRefNFT.mint(userAddress); assertEq(userAddress, testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); // TODO why doesn't this cover the branch != address(0) - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, user2Address)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); vm.prank(user2Address); uint256 patchTokenId = prot.createPatch(address(testBaseNFT), baseTokenId, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); // must have user patch enabled patchTokenId = prot.createPatch(address(testBaseNFT), baseTokenId, address(testPatchLiteRefNFT)); vm.prank(scopeOwner); patchTokenId = prot.createPatch(address(testBaseNFT), baseTokenId, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); // can't call directly testFragmentLiteRefNFT.assign(fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); // must be owner/manager prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); vm.prank(scopeOwner); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); assertEq(userAddress, testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); // TODO why doesn't this cover the branch != address(0) - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.FragmentAlreadyAssigned.selector, address(testFragmentLiteRefNFT), fragmentTokenId)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(testFragmentLiteRefNFT), fragmentTokenId)); vm.prank(scopeOwner); // not normal to call directly but need to test the correct error testFragmentLiteRefNFT.assign(fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); // can't call directly testFragmentLiteRefNFT.unassign(fragmentTokenId); uint256 newFrag = testFragmentLiteRefNFT.mint(userAddress); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, defaultUser)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, defaultUser)); testPatchLiteRefNFT.redactReferenceAddress(refIdx); vm.prank(scopeOwner); testPatchLiteRefNFT.redactReferenceAddress(refIdx); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.FragmentRedacted.selector, address(testFragmentLiteRefNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentRedacted.selector, address(testFragmentLiteRefNFT))); vm.prank(scopeOwner); prot.assignNFT(address(testFragmentLiteRefNFT), newFrag, address(testPatchLiteRefNFT), patchTokenId); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, defaultUser)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, defaultUser)); testPatchLiteRefNFT.unredactReferenceAddress(refIdx); vm.prank(scopeOwner); testPatchLiteRefNFT.unredactReferenceAddress(refIdx); @@ -259,13 +259,13 @@ contract PatchworkNFTBaseTest is Test { vm.startPrank(scopeOwner); uint8 refIdx = testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); assertEq(1, refIdx); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.FragmentAlreadyRegistered.selector, address(testFragmentLiteRefNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyRegistered.selector, address(testFragmentLiteRefNFT))); testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); // Fill ID 2 to 254 then test overflow for (uint8 i = 2; i < 255; i++) { refIdx = testPatchLiteRefNFT.registerReferenceAddress(address(bytes20(uint160(i)))); } - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.OutOfIDs.selector)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.OutOfIDs.selector)); refIdx = testPatchLiteRefNFT.registerReferenceAddress(address(256)); } @@ -273,7 +273,7 @@ contract PatchworkNFTBaseTest is Test { uint256 baseTokenId = testBaseNFT.mint(userAddress); vm.prank(scopeOwner); uint256 patchTokenId = prot.createPatch(address(testBaseNFT), baseTokenId, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.UnsupportedOperation.selector)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedOperation.selector)); testPatchLiteRefNFT.burn(patchTokenId); } @@ -296,7 +296,7 @@ contract PatchworkNFTBaseTest is Test { assertEq((uint256(r1) << 56) + 1, ref); (ref, redacted) = testFragmentLiteRefNFT.getLiteReference(address(1), 0xFFFFFFFFFFFFFF); assertEq((uint256(r1) << 56) + 0xFFFFFFFFFFFFFF, ref); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.UnsupportedTokenId.selector, 1 << 56)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedTokenId.selector, 1 << 56)); testFragmentLiteRefNFT.getLiteReference(address(1), 1 << 56); } diff --git a/test/PatchworkNFTInterface.t.sol b/test/PatchworkNFTInterface.t.sol index 2859e7a..ab96c77 100644 --- a/test/PatchworkNFTInterface.t.sol +++ b/test/PatchworkNFTInterface.t.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import "../src/PatchworkNFTInterface.sol"; import "../src/sampleNFTs/TestPatchLiteRefNFT.sol"; import "../src/sampleNFTs/TestFragmentLiteRefNFT.sol"; diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index 54b37b5..6ddd266 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -52,11 +52,11 @@ contract PatchworkProtocolTest is Test { vm.startPrank(scopeOwner); prot.claimScope(scopeName); assertEq(prot.getScopeOwner(scopeName), scopeOwner); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.ScopeExists.selector, scopeName)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeExists.selector, scopeName)); prot.claimScope(scopeName); vm.stopPrank(); // Current user is not scope owner so can't transfer it - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, defaultUser)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, defaultUser)); prot.transferScopeOwnership(scopeName, address(2)); // Real owner can transfer it vm.prank(scopeOwner); @@ -69,7 +69,7 @@ contract PatchworkProtocolTest is Test { prot.transferScopeOwnership(scopeName, address(2)); assertEq(prot.getScopeOwnerElect(scopeName), address(2)); // Non-owner may not cancel the transfer - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, address(10))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, address(10))); vm.prank(address(10)); prot.cancelScopeTransfer(scopeName); // Real owner can cancel the transfer @@ -80,7 +80,7 @@ contract PatchworkProtocolTest is Test { vm.prank(scopeOwner); prot.transferScopeOwnership(scopeName, address(2)); // User 10 is not elect and may not accept the transfer - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, address(10))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, address(10))); vm.prank(address(10)); prot.acceptScopeTransfer(scopeName); // Finally real elect accepts scope transfer @@ -89,7 +89,7 @@ contract PatchworkProtocolTest is Test { assertEq(prot.getScopeOwner(scopeName), address(2)); assertEq(prot.getScopeOwnerElect(scopeName), address(0)); // Old owner may not transfer it - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, scopeOwner)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, scopeOwner)); vm.prank(scopeOwner); prot.transferScopeOwnership(scopeName, address(2)); // New owner may transfer it back to old owner @@ -99,21 +99,21 @@ contract PatchworkProtocolTest is Test { prot.acceptScopeTransfer(scopeName); assertEq(prot.getScopeOwner(scopeName), scopeOwner); // Non-owner may not add operator - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, address(2))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, address(2))); vm.prank(address(2)); prot.addOperator(scopeName, address(2)); // Real owner may add operator vm.prank(scopeOwner); prot.addOperator(scopeName, address(2)); // Non-owner may not remove operator - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, address(2))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, address(2))); vm.prank(address(2)); prot.removeOperator(scopeName, address(2)); // Real owner may remove operator vm.prank(scopeOwner); prot.removeOperator(scopeName, address(2)); // Non-owner may not set scope rules - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, address(2))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, address(2))); vm.prank(address(2)); prot.setScopeRules(scopeName, true, true, true); } @@ -131,7 +131,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(scopeOwner); prot.claimScope(scopeName); uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotWhitelisted.selector, scopeName, address(testPatchLiteRefNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, scopeName, address(testPatchLiteRefNFT))); prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); } @@ -139,7 +139,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(scopeOwner); prot.claimScope(scopeName); vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); prot.addWhitelist(scopeName, address(testPatchLiteRefNFT)); vm.startPrank(scopeOwner); @@ -148,12 +148,12 @@ contract PatchworkProtocolTest is Test { uint256 tokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); assertEq(tokenId, 0); vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); prot.removeWhitelist(scopeName, address(testPatchLiteRefNFT)); vm.startPrank(scopeOwner); prot.removeWhitelist(scopeName, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotWhitelisted.selector, scopeName, address(testPatchLiteRefNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, scopeName, address(testPatchLiteRefNFT))); tokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId + 1, address(testPatchLiteRefNFT)); } @@ -169,14 +169,14 @@ contract PatchworkProtocolTest is Test { testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); vm.stopPrank(); vm.startPrank(userAddress); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); vm.stopPrank(); vm.prank(scopeOwner); prot.setScopeRules(scopeName, true, false, true); vm.startPrank(userAddress); patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); vm.prank(scopeOwner); @@ -185,7 +185,7 @@ contract PatchworkProtocolTest is Test { prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); // expect revert vm.prank(userAddress); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.TransferBlockedByAssignment.selector, testFragmentLiteRefNFT, fragmentTokenId)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TransferBlockedByAssignment.selector, testFragmentLiteRefNFT, fragmentTokenId)); testFragmentLiteRefNFT.transferFrom(userAddress, address(5), fragmentTokenId); } @@ -194,30 +194,30 @@ contract PatchworkProtocolTest is Test { prot.assignNFT(address(1), 1, address(1), 1); uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); vm.startPrank(scopeOwner); prot.claimScope(scopeName); prot.setScopeRules(scopeName, false, false, false); uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.AlreadyPatched.selector, address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.AlreadyPatched.selector, address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT))); patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); uint256 fragmentTokenId = testFragmentLiteRefNFT.mint(userAddress); assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), userAddress); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.FragmentUnregistered.selector, address(testFragmentLiteRefNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(testFragmentLiteRefNFT))); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); //Register artifactNFT to testPatchLiteRefNFT testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.SelfAssignmentNotAllowed.selector, address(testFragmentLiteRefNFT), fragmentTokenId)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.SelfAssignmentNotAllowed.selector, address(testFragmentLiteRefNFT), fragmentTokenId)); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testFragmentLiteRefNFT), fragmentTokenId); vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); // cover called from non-owner/op with no allowUserAssign prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); @@ -230,13 +230,13 @@ contract PatchworkProtocolTest is Test { assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), userAddress); testFragmentLiteRefNFT.setTestLockOverride(true); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.FragmentAlreadyAssignedInScope.selector, scopeName, address(testFragmentLiteRefNFT), fragmentTokenId)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssignedInScope.selector, scopeName, address(testFragmentLiteRefNFT), fragmentTokenId)); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); testFragmentLiteRefNFT.setTestLockOverride(false); vm.stopPrank(); vm.prank(userAddress); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.TransferNotAllowed.selector, address(testPatchLiteRefNFT), patchTokenId)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TransferNotAllowed.selector, address(testPatchLiteRefNFT), patchTokenId)); testPatchLiteRefNFT.transferFrom(userAddress, user2Address, patchTokenId); } @@ -247,15 +247,15 @@ contract PatchworkProtocolTest is Test { uint256 fragmentTokenId2 = testFragmentLiteRefNFT.mint(userAddress); //Register testPatchLiteRefNFT to testPatchLiteRefNFT testFragmentLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId1, address(testFragmentLiteRefNFT), fragmentTokenId2); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); prot.unassignNFT(address(testFragmentLiteRefNFT), fragmentTokenId1); address[] memory fragmentAddresses = new address[](1); uint256[] memory fragments = new uint256[](1); fragmentAddresses[0] = address(testFragmentLiteRefNFT); fragments[0] = fragmentTokenId1; - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); prot.batchAssignNFT(fragmentAddresses, fragments, address(testFragmentLiteRefNFT), fragmentTokenId2); } @@ -265,7 +265,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(maliciousActor); prot.claimScope("foo"); prot.addOperator("foo", address(4)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.ScopeTransferNotAllowed.selector, address(0))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeTransferNotAllowed.selector, address(0))); prot.transferScopeOwnership("foo", address(0)); } @@ -286,21 +286,21 @@ contract PatchworkProtocolTest is Test { uint256 user2FragmentTokenId = testFragmentLiteRefNFT.mint(user2Address); assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), userAddress); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotWhitelisted.selector, scopeName, address(testFragmentLiteRefNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, scopeName, address(testFragmentLiteRefNFT))); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); vm.prank(scopeOwner); prot.addWhitelist(scopeName, address(testFragmentLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, user2Address)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); vm.prank(user2Address); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, user2Address)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); vm.prank(user2Address); prot.assignNFT(address(testFragmentLiteRefNFT), user2FragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.startPrank(userAddress); prot.assignNFT(address(testFragmentLiteRefNFT), user2FragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); @@ -310,12 +310,12 @@ contract PatchworkProtocolTest is Test { assertEq(tokenId, patchTokenId); assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), userAddress); vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, user2Address)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); vm.prank(user2Address); prot.unassignNFT(address(testFragmentLiteRefNFT), fragmentTokenId); vm.startPrank(userAddress); prot.unassignNFT(address(testFragmentLiteRefNFT), fragmentTokenId); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.FragmentNotAssigned.selector, address(testFragmentLiteRefNFT), fragmentTokenId)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssigned.selector, address(testFragmentLiteRefNFT), fragmentTokenId)); prot.unassignNFT(address(testFragmentLiteRefNFT), fragmentTokenId); } @@ -330,7 +330,7 @@ contract PatchworkProtocolTest is Test { assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), user2Address); //Register artifactNFT to testPatchLiteRefNFT testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, scopeOwner)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, scopeOwner)); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); } @@ -357,7 +357,7 @@ contract PatchworkProtocolTest is Test { prot.assignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), fragment1); // Now Id2 -> Id1 -> Id, unassign Id2 from Id1 vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2); vm.startPrank(scopeOwner); @@ -380,7 +380,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(scopeOwner); testFragmentLiteRefNFT.setGetLiteRefOverride(true, 0); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.FragmentUnregistered.selector, address(testFragmentLiteRefNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(testFragmentLiteRefNFT))); prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2); testFragmentLiteRefNFT.setGetLiteRefOverride(false, 0); @@ -389,7 +389,7 @@ contract PatchworkProtocolTest is Test { assertEq(testFragmentLiteRefNFT.ownerOf(fragment2), address(7)); testFragmentLiteRefNFT.setGetAssignedToOverride(true, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.RefNotFoundInScope.selector, scopeName, address(testPatchLiteRefNFT), address(testFragmentLiteRefNFT), fragment2)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.RefNotFoundInScope.selector, scopeName, address(testPatchLiteRefNFT), address(testFragmentLiteRefNFT), fragment2)); prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2); testFragmentLiteRefNFT.setGetAssignedToOverride(false, address(testPatchLiteRefNFT)); vm.stopPrank(); @@ -397,7 +397,7 @@ contract PatchworkProtocolTest is Test { // try to transfer a patch directly - it should be blocked because it is soulbound assertEq(address(7), testPatchLiteRefNFT.ownerOf(patchTokenId)); // Report as soulbound vm.startPrank(userAddress); // Prank from underlying owner address - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.TransferNotAllowed.selector, address(testPatchLiteRefNFT), patchTokenId)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TransferNotAllowed.selector, address(testPatchLiteRefNFT), patchTokenId)); testPatchLiteRefNFT.transferFrom(userAddress, address(7), patchTokenId); vm.stopPrank(); } @@ -417,30 +417,30 @@ contract PatchworkProtocolTest is Test { fragments[i] = testFragmentLiteRefNFT.mint(userAddress); } - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.FragmentUnregistered.selector, address(testFragmentLiteRefNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(testFragmentLiteRefNFT))); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); uint8 refId = testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); vm.prank(scopeOwner); prot.setScopeRules(scopeName, false, false, true); vm.startPrank(scopeOwner); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotWhitelisted.selector, scopeName, address(testFragmentLiteRefNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, scopeName, address(testFragmentLiteRefNFT))); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); prot.addWhitelist(scopeName, address(testFragmentLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.BadInputLengths.selector)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.BadInputLengths.selector)); prot.batchAssignNFT(new address[](1), fragments, address(testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); vm.prank(userAddress); testPatchLiteRefNFT.setFrozen(patchTokenId, true); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.Frozen.selector, address(testPatchLiteRefNFT), patchTokenId)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testPatchLiteRefNFT), patchTokenId)); vm.prank(scopeOwner); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); vm.prank(userAddress); @@ -448,7 +448,7 @@ contract PatchworkProtocolTest is Test { vm.prank(userAddress); testFragmentLiteRefNFT.setFrozen(fragments[0], true); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragments[0])); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragments[0])); vm.prank(scopeOwner); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); vm.prank(userAddress); @@ -456,7 +456,7 @@ contract PatchworkProtocolTest is Test { vm.prank(scopeOwner); testPatchLiteRefNFT.redactReferenceAddress(refId); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.FragmentRedacted.selector, address(testFragmentLiteRefNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentRedacted.selector, address(testFragmentLiteRefNFT))); vm.prank(scopeOwner); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); vm.prank(scopeOwner); @@ -466,13 +466,13 @@ contract PatchworkProtocolTest is Test { uint256[] memory selfFrag = new uint256[](1); selfAddr[0] = address(testPatchLiteRefNFT); selfFrag[0] = patchTokenId; - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.SelfAssignmentNotAllowed.selector, selfAddr[0], selfFrag[0])); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.SelfAssignmentNotAllowed.selector, selfAddr[0], selfFrag[0])); vm.prank(scopeOwner); prot.batchAssignNFT(selfAddr, selfFrag, address(testPatchLiteRefNFT), patchTokenId); vm.prank(userAddress); testFragmentLiteRefNFT.setLocked(fragments[0], true); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.Locked.selector, address(testFragmentLiteRefNFT), fragments[0])); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Locked.selector, address(testFragmentLiteRefNFT), fragments[0])); vm.prank(scopeOwner); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); vm.prank(userAddress); @@ -483,7 +483,7 @@ contract PatchworkProtocolTest is Test { uint256[] memory otherUserFrag = new uint256[](1); otherUserAddr[0] = address(testFragmentLiteRefNFT); otherUserFrag[0] = testFragmentLiteRefNFT.mint(user2Address); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, scopeOwner)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, scopeOwner)); vm.prank(scopeOwner); prot.batchAssignNFT(otherUserAddr, otherUserFrag, address(testPatchLiteRefNFT), patchTokenId); @@ -499,7 +499,7 @@ contract PatchworkProtocolTest is Test { testFragmentLiteRefNFT.setTestLockOverride(true); // setup for next test part } - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.FragmentAlreadyAssignedInScope.selector, scopeName, fragmentAddresses[0], fragments[0])); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssignedInScope.selector, scopeName, fragmentAddresses[0], fragments[0])); vm.prank(scopeOwner); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); } @@ -526,15 +526,15 @@ contract PatchworkProtocolTest is Test { } vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, user2Address)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); vm.prank(user2Address); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, user2Address)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); vm.prank(user2Address); prot.batchAssignNFT(fragmentAddresses, user2Fragments, address(testPatchLiteRefNFT), patchTokenId); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); prot.batchAssignNFT(fragmentAddresses, user2Fragments, address(testPatchLiteRefNFT), patchTokenId); @@ -543,7 +543,7 @@ contract PatchworkProtocolTest is Test { uint256[] memory otherUserFrag = new uint256[](1); otherUserAddr[0] = address(testFragmentLiteRefNFT); otherUserFrag[0] = testFragmentLiteRefNFT.mint(user2Address); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, userAddress)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); prot.batchAssignNFT(otherUserAddr, otherUserFrag, address(testPatchLiteRefNFT), patchTokenId); @@ -660,11 +660,11 @@ contract PatchworkProtocolTest is Test { testFragmentLiteRefNFT.setLocked(fragment1, false); // only owner may lock - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, user2Address)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); vm.prank(user2Address); testFragmentLiteRefNFT.setLocked(fragment1, true); // only owner may lock - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotAuthorized.selector, user2Address)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); vm.prank(user2Address); testPatchworkNFT.setLocked(1, true); @@ -714,7 +714,7 @@ contract PatchworkProtocolTest is Test { vm.prank(userAddress); testPatchLiteRefNFT.setFrozen(patchTokenId, true); assertEq(0, testPatchLiteRefNFT.getFreezeNonce(patchTokenId)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.Frozen.selector, address(testPatchLiteRefNFT), patchTokenId)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testPatchLiteRefNFT), patchTokenId)); vm.prank(scopeOwner); // Assign Id1 -> Id prot.assignNFT(address(testFragmentLiteRefNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); @@ -732,7 +732,7 @@ contract PatchworkProtocolTest is Test { vm.prank(userAddress); testPatchLiteRefNFT.setFrozen(patchTokenId, true); // It will return that the fragment is frozen even though the patch is the root cause, because all assigned to the patch inherit the freeze - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragment2)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragment2)); vm.prank(scopeOwner); // Now Id2 -> Id1 -> Id, unassign Id2 from Id1 prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2); @@ -751,12 +751,12 @@ contract PatchworkProtocolTest is Test { // Lock the fragment, shouldn't allow assignment to anything vm.prank(userAddress); testFragmentLiteRefNFT.setFrozen(fragment1, true); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragment1)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragment1)); vm.prank(scopeOwner); prot.assignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), fragment1); vm.prank(userAddress); testFragmentLiteRefNFT.setFrozen(fragment2, true); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragment1)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragment1)); vm.prank(scopeOwner); prot.assignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), fragment1); vm.startPrank(userAddress); @@ -767,12 +767,12 @@ contract PatchworkProtocolTest is Test { uint256 nonce = testFragmentLiteRefNFT.getFreezeNonce(fragment2); testFragmentLiteRefNFT.setFrozen(fragment2, true); testFragmentLiteRefNFT.setFrozen(fragment2, false); // nonce +1 - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotFrozen.selector, address(testFragmentLiteRefNFT), fragment2)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, address(testFragmentLiteRefNFT), fragment2)); testFragmentLiteRefNFT.transferFromWithFreezeNonce(userAddress, user2Address, fragment2, nonce+1); assertEq(false, testFragmentLiteRefNFT.frozen(fragment2)); testFragmentLiteRefNFT.setFrozen(fragment2, true); assertEq(true, testFragmentLiteRefNFT.frozen(fragment2)); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.IncorrectNonce.selector, address(testFragmentLiteRefNFT), fragment2, nonce)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, address(testFragmentLiteRefNFT), fragment2, nonce)); testFragmentLiteRefNFT.transferFromWithFreezeNonce(userAddress, user2Address, fragment2, nonce); // now success testFragmentLiteRefNFT.transferFromWithFreezeNonce(userAddress, user2Address, fragment2, nonce+1); @@ -789,7 +789,7 @@ contract PatchworkProtocolTest is Test { testFragmentLiteRefNFT.addReference(fragment1, ref); // Should revert with data integrity error vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.DataIntegrityError.selector, address(testFragmentLiteRefNFT), fragment1, address(0), 0)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.DataIntegrityError.selector, address(testFragmentLiteRefNFT), fragment1, address(0), 0)); vm.prank(userAddress); testFragmentLiteRefNFT.transferFrom(userAddress, user2Address, fragment1); } @@ -804,7 +804,7 @@ contract PatchworkProtocolTest is Test { testFragmentLiteRefNFT.addReference(fragment1, ref); // Should revert with data integrity error vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(PatchworkProtocol.NotPatchworkAssignable.selector, address(testBaseNFT))); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotPatchworkAssignable.selector, address(testBaseNFT))); vm.prank(userAddress); testFragmentLiteRefNFT.transferFrom(userAddress, user2Address, fragment1); } From 9238118ad80e70716b119ecd210a223b5978cf93 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Sun, 8 Oct 2023 19:07:15 -0700 Subject: [PATCH 09/63] Refactor - split tests (#31) * Fixing linter errors before splitting to save lots of time * Split off PatchworkNFT specific tests * Split tests * Moved interface tests to specific test contracts and expanded on ERC compliance --- test/PatchworkAccountPatch.t.sol | 17 ++ test/PatchworkFragment.t.sol | 75 ++++++++ test/PatchworkNFT.t.sol | 173 +++++++++++++++++ test/PatchworkNFTBase.t.sol | 303 ------------------------------ test/PatchworkNFTInterface.t.sol | 30 --- test/PatchworkNFTReferences.t.sol | 124 ++++++++++++ test/PatchworkPatch.t.sol | 80 ++++++++ 7 files changed, 469 insertions(+), 333 deletions(-) create mode 100644 test/PatchworkFragment.t.sol create mode 100644 test/PatchworkNFT.t.sol delete mode 100644 test/PatchworkNFTBase.t.sol delete mode 100644 test/PatchworkNFTInterface.t.sol create mode 100644 test/PatchworkNFTReferences.t.sol create mode 100644 test/PatchworkPatch.t.sol diff --git a/test/PatchworkAccountPatch.t.sol b/test/PatchworkAccountPatch.t.sol index 3d42bea..eac5abd 100644 --- a/test/PatchworkAccountPatch.t.sol +++ b/test/PatchworkAccountPatch.t.sol @@ -36,6 +36,23 @@ contract PatchworkAccountPatchTest is Test { vm.stopPrank(); } + function testScopeName() public { + vm.prank(_scopeOwner); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); + assertEq(_scopeName, testAccountPatchNFT.getScopeName()); + } + + function testSupportsInterface() public { + vm.prank(_scopeOwner); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); + assertTrue(testAccountPatchNFT.supportsInterface(type(IERC165).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IERC721).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IERC4906).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IERC5192).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchworkAccountPatch).interfaceId)); + } + function testAccountPatchNotSameOwner() public { // Not same owner model, yes transferrable vm.prank(_scopeOwner); diff --git a/test/PatchworkFragment.t.sol b/test/PatchworkFragment.t.sol new file mode 100644 index 0000000..36431fb --- /dev/null +++ b/test/PatchworkFragment.t.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "../src/PatchworkProtocol.sol"; +import "../src/sampleNFTs/TestFragmentLiteRefNFT.sol"; +import "../src/sampleNFTs/TestBaseNFT.sol"; + +contract PatchworkFragmentTest is Test { + PatchworkProtocol _prot; + TestFragmentLiteRefNFT _testFragmentLiteRefNFT; + + string _scopeName; + address _defaultUser; + address _scopeOwner; + address _patchworkOwner; + address _userAddress; + address _user2Address; + + function setUp() public { + _defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; + _patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; + _userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; + _user2Address = address(550001); + _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; + + vm.prank(_patchworkOwner); + _prot = new PatchworkProtocol(); + _scopeName = "testscope"; + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + + _testFragmentLiteRefNFT = new TestFragmentLiteRefNFT(address(_prot)); + + vm.stopPrank(); + vm.prank(_userAddress); + } + + function testScopeName() public { + assertEq(_scopeName, _testFragmentLiteRefNFT.getScopeName()); + } + + function testSupportsInterface() public { + assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IERC165).interfaceId)); + assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IERC721).interfaceId)); + assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IERC4906).interfaceId)); + assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IERC5192).interfaceId)); + assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); + assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IPatchworkAssignableNFT).interfaceId)); + } + + function testOnAssignedTransferError() public { + vm.expectRevert(); + _testFragmentLiteRefNFT.onAssignedTransfer(address(0), address(1), 1); + } + + function testPatchworkCompatible() public { + bytes2 r2 = _testFragmentLiteRefNFT.patchworkCompatible_(); + assertEq(0, r2); + } + + function testLiteref56bitlimit() public { + vm.prank(_scopeOwner); + uint8 r1 = _testFragmentLiteRefNFT.registerReferenceAddress(address(1)); + (uint64 ref, bool redacted) = _testFragmentLiteRefNFT.getLiteReference(address(1), 1); + assertEq((uint256(r1) << 56) + 1, ref); + (ref, redacted) = _testFragmentLiteRefNFT.getLiteReference(address(1), 0xFFFFFFFFFFFFFF); + assertEq((uint256(r1) << 56) + 0xFFFFFFFFFFFFFF, ref); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedTokenId.selector, 1 << 56)); + _testFragmentLiteRefNFT.getLiteReference(address(1), 1 << 56); + } +} \ No newline at end of file diff --git a/test/PatchworkNFT.t.sol b/test/PatchworkNFT.t.sol new file mode 100644 index 0000000..064e03f --- /dev/null +++ b/test/PatchworkNFT.t.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "../src/PatchworkProtocol.sol"; +import "../src/sampleNFTs/TestPatchworkNFT.sol"; + +contract PatchworkNFTTest is Test { + PatchworkProtocol _prot; + TestPatchworkNFT _testPatchworkNFT; + + string _scopeName; + address _defaultUser; + address _scopeOwner; + address _patchworkOwner; + address _userAddress; + address _user2Address; + + function setUp() public { + _defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; + _patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; + _userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; + _user2Address = address(550001); + _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; + + vm.prank(_patchworkOwner); + _prot = new PatchworkProtocol(); + _scopeName = "testscope"; + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + + _testPatchworkNFT = new TestPatchworkNFT(address(_prot)); + vm.stopPrank(); + } + + function testScopeName() public { + assertEq(_scopeName, _testPatchworkNFT.getScopeName()); + } + + function testSupportsInterface() public { + assertTrue(_testPatchworkNFT.supportsInterface(type(IERC165).interfaceId)); + assertTrue(_testPatchworkNFT.supportsInterface(type(IERC721).interfaceId)); + assertTrue(_testPatchworkNFT.supportsInterface(type(IERC4906).interfaceId)); + assertTrue(_testPatchworkNFT.supportsInterface(type(IERC5192).interfaceId)); + assertTrue(_testPatchworkNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); + } + + function testLoadStorePackedMetadataSlot() public { + _testPatchworkNFT.mint(_userAddress, 1); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _testPatchworkNFT.storePackedMetadataSlot(1, 0, 0x505050); + vm.prank(_scopeOwner); + _testPatchworkNFT.storePackedMetadataSlot(1, 0, 0x505050); + assertEq(0x505050, _testPatchworkNFT.loadPackedMetadataSlot(1, 0)); + } + + function testTransferFrom() public { + // TODO make sure these are calling checkTransfer on proto + _testPatchworkNFT.mint(_userAddress, 1); + assertEq(_userAddress, _testPatchworkNFT.ownerOf(1)); + vm.prank(_userAddress); + _testPatchworkNFT.transferFrom(_userAddress, _user2Address, 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.prank(_user2Address); + _testPatchworkNFT.safeTransferFrom(_user2Address, _userAddress, 1); + assertEq(_userAddress, _testPatchworkNFT.ownerOf(1)); + vm.prank(_userAddress); + _testPatchworkNFT.safeTransferFrom(_userAddress, _user2Address, 1, bytes("abcd")); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + + // test wrong user revert + vm.startPrank(_userAddress); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.expectRevert("ERC721: caller is not token owner or approved"); + _testPatchworkNFT.transferFrom(_user2Address, _userAddress, 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.expectRevert("ERC721: caller is not token owner or approved"); + _testPatchworkNFT.safeTransferFrom(_user2Address, _userAddress, 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.expectRevert("ERC721: caller is not token owner or approved"); + _testPatchworkNFT.safeTransferFrom(_user2Address, _userAddress, 1, bytes("abcd")); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + } + + function testLockFreezeSeparation() public { + _testPatchworkNFT.mint(_userAddress, 1); + vm.startPrank(_userAddress); + assertFalse(_testPatchworkNFT.locked(1)); + _testPatchworkNFT.setLocked(1, true); + assertTrue(_testPatchworkNFT.locked(1)); + assertFalse(_testPatchworkNFT.frozen(1)); + _testPatchworkNFT.setFrozen(1, true); + assertTrue(_testPatchworkNFT.frozen(1)); + assertTrue(_testPatchworkNFT.locked(1)); + _testPatchworkNFT.setLocked(1, false); + assertTrue(_testPatchworkNFT.frozen(1)); + assertFalse(_testPatchworkNFT.locked(1)); + _testPatchworkNFT.setFrozen(1, false); + assertFalse(_testPatchworkNFT.frozen(1)); + assertFalse(_testPatchworkNFT.locked(1)); + _testPatchworkNFT.setFrozen(1, true); + assertTrue(_testPatchworkNFT.frozen(1)); + assertFalse(_testPatchworkNFT.locked(1)); + _testPatchworkNFT.setLocked(1, true); + assertTrue(_testPatchworkNFT.frozen(1)); + assertTrue(_testPatchworkNFT.locked(1)); + } + + function testTransferFromWithFreezeNonce() public { + // TODO make sure these are calling checkTransfer on proto + _testPatchworkNFT.mint(_userAddress, 1); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _testPatchworkNFT.setFrozen(1, true); + vm.prank(_userAddress); + _testPatchworkNFT.setFrozen(1, true); + assertEq(_userAddress, _testPatchworkNFT.ownerOf(1)); + vm.prank(_userAddress); + _testPatchworkNFT.transferFromWithFreezeNonce(_userAddress, _user2Address, 1, 0); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.prank(_user2Address); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, 0); + assertEq(_userAddress, _testPatchworkNFT.ownerOf(1)); + vm.prank(_userAddress); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_userAddress, _user2Address, 1, bytes("abcd"), 0); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + + vm.startPrank(_user2Address); + // test not frozen revert + _testPatchworkNFT.setFrozen(1, false); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, _testPatchworkNFT, 1)); + _testPatchworkNFT.transferFromWithFreezeNonce(_user2Address, _userAddress, 1, 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, _testPatchworkNFT, 1)); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, _testPatchworkNFT, 1)); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, bytes("abcd"), 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + + // test incorrect nonce revert + _testPatchworkNFT.setFrozen(1, true); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, _testPatchworkNFT, 1, 0)); + _testPatchworkNFT.transferFromWithFreezeNonce(_user2Address, _userAddress, 1, 0); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, _testPatchworkNFT, 1, 0)); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, 0); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, _testPatchworkNFT, 1, 0)); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, bytes("abcd"), 0); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.stopPrank(); + + // test wrong user revert + vm.startPrank(_userAddress); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.expectRevert("ERC721: caller is not token owner or approved"); + _testPatchworkNFT.transferFromWithFreezeNonce(_user2Address, _userAddress, 1, 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.expectRevert("ERC721: caller is not token owner or approved"); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.expectRevert("ERC721: caller is not token owner or approved"); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, bytes("abcd"), 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + vm.stopPrank(); + } +} \ No newline at end of file diff --git a/test/PatchworkNFTBase.t.sol b/test/PatchworkNFTBase.t.sol deleted file mode 100644 index 625938a..0000000 --- a/test/PatchworkNFTBase.t.sol +++ /dev/null @@ -1,303 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import "forge-std/console.sol"; - -import "../src/PatchworkProtocol.sol"; -import "../src/sampleNFTs/TestPatchLiteRefNFT.sol"; -import "../src/sampleNFTs/TestFragmentLiteRefNFT.sol"; -import "../src/sampleNFTs/TestBaseNFT.sol"; -import "../src/sampleNFTs/TestPatchworkNFT.sol"; - -contract PatchworkNFTBaseTest is Test { - event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - - PatchworkProtocol prot; - TestBaseNFT testBaseNFT; - TestPatchworkNFT testPatchworkNFT; - TestPatchLiteRefNFT testPatchLiteRefNFT; - TestFragmentLiteRefNFT testFragmentLiteRefNFT; - - string scopeName; - address defaultUser; - address scopeOwner; - address patchworkOwner; - address userAddress; - address user2Address; - - function setUp() public { - defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; - patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; - userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; - user2Address = address(550001); - scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; - - vm.prank(patchworkOwner); - prot = new PatchworkProtocol(); - scopeName = "testscope"; - vm.prank(scopeOwner); - prot.claimScope(scopeName); - vm.prank(scopeOwner); - prot.setScopeRules(scopeName, false, false, false); - - vm.prank(userAddress); - testBaseNFT = new TestBaseNFT(); - - vm.prank(scopeOwner); - testPatchLiteRefNFT = new TestPatchLiteRefNFT(address(prot)); - vm.prank(scopeOwner); - testFragmentLiteRefNFT = new TestFragmentLiteRefNFT(address(prot)); - vm.prank(scopeOwner); - testPatchworkNFT = new TestPatchworkNFT(address(prot)); - } - - function testScopeName() public { - assertEq(scopeName, testPatchworkNFT.getScopeName()); - assertEq(scopeName, testPatchLiteRefNFT.getScopeName()); - assertEq(scopeName, testFragmentLiteRefNFT.getScopeName()); - } - - function testLoadStorePackedMetadataSlot() public { - testPatchworkNFT.mint(userAddress, 1); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, defaultUser)); - testPatchworkNFT.storePackedMetadataSlot(1, 0, 0x505050); - vm.prank(scopeOwner); - testPatchworkNFT.storePackedMetadataSlot(1, 0, 0x505050); - assertEq(0x505050, testPatchworkNFT.loadPackedMetadataSlot(1, 0)); - } - - function testTransferFrom() public { - // TODO make sure these are calling checkTransfer on proto - testPatchworkNFT.mint(userAddress, 1); - assertEq(userAddress, testPatchworkNFT.ownerOf(1)); - vm.prank(userAddress); - testPatchworkNFT.transferFrom(userAddress, user2Address, 1); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.prank(user2Address); - testPatchworkNFT.safeTransferFrom(user2Address, userAddress, 1); - assertEq(userAddress, testPatchworkNFT.ownerOf(1)); - vm.prank(userAddress); - testPatchworkNFT.safeTransferFrom(userAddress, user2Address, 1, bytes("abcd")); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - - // test wrong user revert - vm.startPrank(userAddress); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert("ERC721: caller is not token owner or approved"); - testPatchworkNFT.transferFrom(user2Address, userAddress, 1); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert("ERC721: caller is not token owner or approved"); - testPatchworkNFT.safeTransferFrom(user2Address, userAddress, 1); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert("ERC721: caller is not token owner or approved"); - testPatchworkNFT.safeTransferFrom(user2Address, userAddress, 1, bytes("abcd")); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - } - - function testLockFreezeSeparation() public { - testPatchworkNFT.mint(userAddress, 1); - vm.startPrank(userAddress); - assertFalse(testPatchworkNFT.locked(1)); - testPatchworkNFT.setLocked(1, true); - assertTrue(testPatchworkNFT.locked(1)); - assertFalse(testPatchworkNFT.frozen(1)); - testPatchworkNFT.setFrozen(1, true); - assertTrue(testPatchworkNFT.frozen(1)); - assertTrue(testPatchworkNFT.locked(1)); - testPatchworkNFT.setLocked(1, false); - assertTrue(testPatchworkNFT.frozen(1)); - assertFalse(testPatchworkNFT.locked(1)); - testPatchworkNFT.setFrozen(1, false); - assertFalse(testPatchworkNFT.frozen(1)); - assertFalse(testPatchworkNFT.locked(1)); - testPatchworkNFT.setFrozen(1, true); - assertTrue(testPatchworkNFT.frozen(1)); - assertFalse(testPatchworkNFT.locked(1)); - testPatchworkNFT.setLocked(1, true); - assertTrue(testPatchworkNFT.frozen(1)); - assertTrue(testPatchworkNFT.locked(1)); - } - - function testTransferFromWithFreezeNonce() public { - // TODO make sure these are calling checkTransfer on proto - testPatchworkNFT.mint(userAddress, 1); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, defaultUser)); - testPatchworkNFT.setFrozen(1, true); - vm.prank(userAddress); - testPatchworkNFT.setFrozen(1, true); - assertEq(userAddress, testPatchworkNFT.ownerOf(1)); - vm.prank(userAddress); - testPatchworkNFT.transferFromWithFreezeNonce(userAddress, user2Address, 1, 0); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.prank(user2Address); - testPatchworkNFT.safeTransferFromWithFreezeNonce(user2Address, userAddress, 1, 0); - assertEq(userAddress, testPatchworkNFT.ownerOf(1)); - vm.prank(userAddress); - testPatchworkNFT.safeTransferFromWithFreezeNonce(userAddress, user2Address, 1, bytes("abcd"), 0); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - - vm.startPrank(user2Address); - // test not frozen revert - testPatchworkNFT.setFrozen(1, false); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, testPatchworkNFT, 1)); - testPatchworkNFT.transferFromWithFreezeNonce(user2Address, userAddress, 1, 1); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, testPatchworkNFT, 1)); - testPatchworkNFT.safeTransferFromWithFreezeNonce(user2Address, userAddress, 1, 1); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, testPatchworkNFT, 1)); - testPatchworkNFT.safeTransferFromWithFreezeNonce(user2Address, userAddress, 1, bytes("abcd"), 1); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - - // test incorrect nonce revert - testPatchworkNFT.setFrozen(1, true); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, testPatchworkNFT, 1, 0)); - testPatchworkNFT.transferFromWithFreezeNonce(user2Address, userAddress, 1, 0); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, testPatchworkNFT, 1, 0)); - testPatchworkNFT.safeTransferFromWithFreezeNonce(user2Address, userAddress, 1, 0); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, testPatchworkNFT, 1, 0)); - testPatchworkNFT.safeTransferFromWithFreezeNonce(user2Address, userAddress, 1, bytes("abcd"), 0); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.stopPrank(); - - // test wrong user revert - vm.startPrank(userAddress); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert("ERC721: caller is not token owner or approved"); - testPatchworkNFT.transferFromWithFreezeNonce(user2Address, userAddress, 1, 1); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert("ERC721: caller is not token owner or approved"); - testPatchworkNFT.safeTransferFromWithFreezeNonce(user2Address, userAddress, 1, 1); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.expectRevert("ERC721: caller is not token owner or approved"); - testPatchworkNFT.safeTransferFromWithFreezeNonce(user2Address, userAddress, 1, bytes("abcd"), 1); - assertEq(user2Address, testPatchworkNFT.ownerOf(1)); - vm.stopPrank(); - } - - function testLocks() public { - uint256 baseTokenId = testBaseNFT.mint(userAddress); - vm.prank(scopeOwner); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), baseTokenId, address(testPatchLiteRefNFT)); - bool locked = testPatchLiteRefNFT.locked(patchTokenId); - assertFalse(locked); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.CannotLockSoulboundPatch.selector, testPatchLiteRefNFT)); - testPatchLiteRefNFT.setLocked(patchTokenId, true); - } - - function testReferenceAddresses() public { - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, defaultUser)); - uint8 refIdx = testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - (uint64 ref, bool redacted) = testPatchLiteRefNFT.getLiteReference(address(testFragmentLiteRefNFT), 1); - assertEq(0, ref); - vm.prank(scopeOwner); - refIdx = testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - (uint8 _id, bool _redacted) = testPatchLiteRefNFT.getReferenceId(address(testFragmentLiteRefNFT)); - assertEq(refIdx, _id); - assertFalse(_redacted); - (address _addr, bool _redacted2) = testPatchLiteRefNFT.getReferenceAddress(refIdx); - assertEq(address(testFragmentLiteRefNFT), _addr); - assertFalse(_redacted2); - (ref, redacted) = testPatchLiteRefNFT.getLiteReference(address(testFragmentLiteRefNFT), 1); - (address refAddr, uint256 tokenId) = testPatchLiteRefNFT.getReferenceAddressAndTokenId(ref); - assertEq(address(testFragmentLiteRefNFT), refAddr); - assertEq(1, tokenId); - - // test assign perms - uint256 baseTokenId = testBaseNFT.mint(userAddress); - uint256 fragmentTokenId = testFragmentLiteRefNFT.mint(userAddress); - assertEq(userAddress, testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); // TODO why doesn't this cover the branch != address(0) - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); - vm.prank(user2Address); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), baseTokenId, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - vm.prank(userAddress); // must have user patch enabled - patchTokenId = prot.createPatch(address(testBaseNFT), baseTokenId, address(testPatchLiteRefNFT)); - vm.prank(scopeOwner); - patchTokenId = prot.createPatch(address(testBaseNFT), baseTokenId, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - vm.prank(userAddress); // can't call directly - testFragmentLiteRefNFT.assign(fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - vm.prank(userAddress); // must be owner/manager - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); - - vm.prank(scopeOwner); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); - assertEq(userAddress, testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); // TODO why doesn't this cover the branch != address(0) - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(testFragmentLiteRefNFT), fragmentTokenId)); - vm.prank(scopeOwner); // not normal to call directly but need to test the correct error - testFragmentLiteRefNFT.assign(fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - vm.prank(userAddress); // can't call directly - testFragmentLiteRefNFT.unassign(fragmentTokenId); - - uint256 newFrag = testFragmentLiteRefNFT.mint(userAddress); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, defaultUser)); - testPatchLiteRefNFT.redactReferenceAddress(refIdx); - vm.prank(scopeOwner); - testPatchLiteRefNFT.redactReferenceAddress(refIdx); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentRedacted.selector, address(testFragmentLiteRefNFT))); - vm.prank(scopeOwner); - prot.assignNFT(address(testFragmentLiteRefNFT), newFrag, address(testPatchLiteRefNFT), patchTokenId); - - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, defaultUser)); - testPatchLiteRefNFT.unredactReferenceAddress(refIdx); - vm.prank(scopeOwner); - testPatchLiteRefNFT.unredactReferenceAddress(refIdx); - vm.prank(scopeOwner); - prot.assignNFT(address(testFragmentLiteRefNFT), newFrag, address(testPatchLiteRefNFT), patchTokenId); - } - - function testReferenceAddressErrors() public { - vm.startPrank(scopeOwner); - uint8 refIdx = testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - assertEq(1, refIdx); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyRegistered.selector, address(testFragmentLiteRefNFT))); - testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - // Fill ID 2 to 254 then test overflow - for (uint8 i = 2; i < 255; i++) { - refIdx = testPatchLiteRefNFT.registerReferenceAddress(address(bytes20(uint160(i)))); - } - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.OutOfIDs.selector)); - refIdx = testPatchLiteRefNFT.registerReferenceAddress(address(256)); - } - - function testBurn() public { - uint256 baseTokenId = testBaseNFT.mint(userAddress); - vm.prank(scopeOwner); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), baseTokenId, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedOperation.selector)); - testPatchLiteRefNFT.burn(patchTokenId); - } - - function testOnAssignedTransferError() public { - vm.expectRevert(); - testFragmentLiteRefNFT.onAssignedTransfer(address(0), address(1), 1); - } - - function testPatchworkCompatible() public { - bytes1 r1 = testPatchLiteRefNFT.patchworkCompatible_(); - assertEq(0, r1); - bytes2 r2 = testFragmentLiteRefNFT.patchworkCompatible_(); - assertEq(0, r2); - } - - function testLiteref56bitlimit() public { - vm.prank(scopeOwner); - uint8 r1 = testFragmentLiteRefNFT.registerReferenceAddress(address(1)); - (uint64 ref, bool redacted) = testFragmentLiteRefNFT.getLiteReference(address(1), 1); - assertEq((uint256(r1) << 56) + 1, ref); - (ref, redacted) = testFragmentLiteRefNFT.getLiteReference(address(1), 0xFFFFFFFFFFFFFF); - assertEq((uint256(r1) << 56) + 0xFFFFFFFFFFFFFF, ref); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedTokenId.selector, 1 << 56)); - testFragmentLiteRefNFT.getLiteReference(address(1), 1 << 56); - } - -} \ No newline at end of file diff --git a/test/PatchworkNFTInterface.t.sol b/test/PatchworkNFTInterface.t.sol deleted file mode 100644 index ab96c77..0000000 --- a/test/PatchworkNFTInterface.t.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import "forge-std/console.sol"; - -import "../src/sampleNFTs/TestPatchLiteRefNFT.sol"; -import "../src/sampleNFTs/TestFragmentLiteRefNFT.sol"; - -contract PatchworkNFTInterfaceTest is Test { - TestPatchLiteRefNFT testPatchLiteRefNFT; - TestFragmentLiteRefNFT testFragmentLiteRefNFT; - - function setUp() public { - testPatchLiteRefNFT = new TestPatchLiteRefNFT(address(1)); - testFragmentLiteRefNFT = new TestFragmentLiteRefNFT(0x0000000000000000000000000000000000000000); - } - - function testSupportsInterface() public { - assertTrue(testPatchLiteRefNFT.supportsInterface(type(IERC165).interfaceId)); - assertTrue(testPatchLiteRefNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); - assertTrue(testPatchLiteRefNFT.supportsInterface(type(IPatchworkLiteRef).interfaceId)); - assertTrue(testPatchLiteRefNFT.supportsInterface(type(IPatchworkPatch).interfaceId)); - - assertTrue(testFragmentLiteRefNFT.supportsInterface(type(IERC165).interfaceId)); - assertTrue(testFragmentLiteRefNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); - assertTrue(testFragmentLiteRefNFT.supportsInterface(type(IPatchworkAssignableNFT).interfaceId)); - } - -} \ No newline at end of file diff --git a/test/PatchworkNFTReferences.t.sol b/test/PatchworkNFTReferences.t.sol new file mode 100644 index 0000000..7d57425 --- /dev/null +++ b/test/PatchworkNFTReferences.t.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "../src/PatchworkProtocol.sol"; +import "../src/sampleNFTs/TestPatchLiteRefNFT.sol"; +import "../src/sampleNFTs/TestFragmentLiteRefNFT.sol"; +import "../src/sampleNFTs/TestBaseNFT.sol"; + +contract PatchworkNFTCombinedTest is Test { + PatchworkProtocol _prot; + TestBaseNFT _testBaseNFT; + TestPatchLiteRefNFT _testPatchLiteRefNFT; + TestFragmentLiteRefNFT _testFragmentLiteRefNFT; + + string _scopeName; + address _defaultUser; + address _scopeOwner; + address _patchworkOwner; + address _userAddress; + address _user2Address; + + function setUp() public { + _defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; + _patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; + _userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; + _user2Address = address(550001); + _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; + + vm.prank(_patchworkOwner); + _prot = new PatchworkProtocol(); + _scopeName = "testscope"; + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + + _testPatchLiteRefNFT = new TestPatchLiteRefNFT(address(_prot)); + _testFragmentLiteRefNFT = new TestFragmentLiteRefNFT(address(_prot)); + + vm.stopPrank(); + vm.prank(_userAddress); + _testBaseNFT = new TestBaseNFT(); + } + + function testReferenceAddresses() public { + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + uint8 refIdx = _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); + (uint64 ref, bool redacted) = _testPatchLiteRefNFT.getLiteReference(address(_testFragmentLiteRefNFT), 1); + assertEq(0, ref); + vm.prank(_scopeOwner); + refIdx = _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); + (uint8 _id, bool _redacted) = _testPatchLiteRefNFT.getReferenceId(address(_testFragmentLiteRefNFT)); + assertEq(refIdx, _id); + assertFalse(_redacted); + (address _addr, bool _redacted2) = _testPatchLiteRefNFT.getReferenceAddress(refIdx); + assertEq(address(_testFragmentLiteRefNFT), _addr); + assertFalse(_redacted2); + (ref, redacted) = _testPatchLiteRefNFT.getLiteReference(address(_testFragmentLiteRefNFT), 1); + (address refAddr, uint256 tokenId) = _testPatchLiteRefNFT.getReferenceAddressAndTokenId(ref); + assertEq(address(_testFragmentLiteRefNFT), refAddr); + assertEq(1, tokenId); + + // test assign perms + uint256 baseTokenId = _testBaseNFT.mint(_userAddress); + uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress); + assertEq(_userAddress, _testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); // TODO why doesn't this cover the branch != address(0) + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + vm.prank(_user2Address); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); // must have user patch enabled + patchTokenId = _prot.createPatch(address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + vm.prank(_scopeOwner); + patchTokenId = _prot.createPatch(address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); // can't call directly + _testFragmentLiteRefNFT.assign(fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); // must be owner/manager + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + + vm.prank(_scopeOwner); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + assertEq(_userAddress, _testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); // TODO why doesn't this cover the branch != address(0) + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(_testFragmentLiteRefNFT), fragmentTokenId)); + vm.prank(_scopeOwner); // not normal to call directly but need to test the correct error + _testFragmentLiteRefNFT.assign(fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); // can't call directly + _testFragmentLiteRefNFT.unassign(fragmentTokenId); + + uint256 newFrag = _testFragmentLiteRefNFT.mint(_userAddress); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _testPatchLiteRefNFT.redactReferenceAddress(refIdx); + vm.prank(_scopeOwner); + _testPatchLiteRefNFT.redactReferenceAddress(refIdx); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentRedacted.selector, address(_testFragmentLiteRefNFT))); + vm.prank(_scopeOwner); + _prot.assignNFT(address(_testFragmentLiteRefNFT), newFrag, address(_testPatchLiteRefNFT), patchTokenId); + + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _testPatchLiteRefNFT.unredactReferenceAddress(refIdx); + vm.prank(_scopeOwner); + _testPatchLiteRefNFT.unredactReferenceAddress(refIdx); + vm.prank(_scopeOwner); + _prot.assignNFT(address(_testFragmentLiteRefNFT), newFrag, address(_testPatchLiteRefNFT), patchTokenId); + } + + function testReferenceAddressErrors() public { + vm.startPrank(_scopeOwner); + uint8 refIdx = _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); + assertEq(1, refIdx); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyRegistered.selector, address(_testFragmentLiteRefNFT))); + _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); + // Fill ID 2 to 254 then test overflow + for (uint8 i = 2; i < 255; i++) { + refIdx = _testPatchLiteRefNFT.registerReferenceAddress(address(bytes20(uint160(i)))); + } + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.OutOfIDs.selector)); + refIdx = _testPatchLiteRefNFT.registerReferenceAddress(address(256)); + } +} \ No newline at end of file diff --git a/test/PatchworkPatch.t.sol b/test/PatchworkPatch.t.sol new file mode 100644 index 0000000..000c29f --- /dev/null +++ b/test/PatchworkPatch.t.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "../src/PatchworkProtocol.sol"; +import "../src/sampleNFTs/TestPatchLiteRefNFT.sol"; +import "../src/sampleNFTs/TestBaseNFT.sol"; + +contract PatchworkPatchTest is Test { + PatchworkProtocol _prot; + TestBaseNFT _testBaseNFT; + TestPatchLiteRefNFT _testPatchLiteRefNFT; + + string _scopeName; + address _defaultUser; + address _scopeOwner; + address _patchworkOwner; + address _userAddress; + address _user2Address; + + function setUp() public { + _defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; + _patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; + _userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; + _user2Address = address(550001); + _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; + + vm.prank(_patchworkOwner); + _prot = new PatchworkProtocol(); + _scopeName = "testscope"; + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + + _testPatchLiteRefNFT = new TestPatchLiteRefNFT(address(_prot)); + + vm.stopPrank(); + vm.prank(_userAddress); + _testBaseNFT = new TestBaseNFT(); + } + + function testScopeName() public { + assertEq(_scopeName, _testPatchLiteRefNFT.getScopeName()); + } + + function testSupportsInterface() public { + assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IERC165).interfaceId)); + assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IERC721).interfaceId)); + assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IERC4906).interfaceId)); + assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IERC5192).interfaceId)); + assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); + assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IPatchworkLiteRef).interfaceId)); + assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IPatchworkPatch).interfaceId)); + } + + function testLocks() public { + uint256 baseTokenId = _testBaseNFT.mint(_userAddress); + vm.prank(_scopeOwner); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + bool locked = _testPatchLiteRefNFT.locked(patchTokenId); + assertFalse(locked); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.CannotLockSoulboundPatch.selector, _testPatchLiteRefNFT)); + _testPatchLiteRefNFT.setLocked(patchTokenId, true); + } + + function testBurn() public { + uint256 baseTokenId = _testBaseNFT.mint(_userAddress); + vm.prank(_scopeOwner); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedOperation.selector)); + _testPatchLiteRefNFT.burn(patchTokenId); + } + + function testPatchworkCompatible() public { + bytes1 r1 = _testPatchLiteRefNFT.patchworkCompatible_(); + assertEq(0, r1); + } +} \ No newline at end of file From b518a9cfe891bfb9865a0db28b11d782bc44309f Mon Sep 17 00:00:00 2001 From: Rob Green Date: Thu, 26 Oct 2023 12:02:21 -0700 Subject: [PATCH 10/63] Unindexed scope name (#34) --- src/IPatchworkProtocol.sol | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index 700ac8c..2771baf 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -309,7 +309,7 @@ interface IPatchworkProtocol { @param scopeName The name of the claimed scope @param owner The owner of the scope */ - event ScopeClaim(string indexed scopeName, address indexed owner); + event ScopeClaim(string scopeName, address indexed owner); /** @notice Emitted when a scope has elected a new owner to transfer to @@ -317,7 +317,7 @@ interface IPatchworkProtocol { @param from The owner of the scope @param to The owner-elect of the scope */ - event ScopeTransferElect(string indexed scopeName, address indexed from, address indexed to); + event ScopeTransferElect(string scopeName, address indexed from, address indexed to); /** @notice Emitted when a scope transfer is canceled @@ -325,7 +325,7 @@ interface IPatchworkProtocol { @param from The owner of the scope @param to The owner-elect of the scope */ - event ScopeTransferCancel(string indexed scopeName, address indexed from, address indexed to); + event ScopeTransferCancel(string scopeName, address indexed from, address indexed to); /** @notice Emitted when a scope is transferred @@ -333,7 +333,7 @@ interface IPatchworkProtocol { @param from The address transferring the scope @param to The recipient of the scope */ - event ScopeTransfer(string indexed scopeName, address indexed from, address indexed to); + event ScopeTransfer(string scopeName, address indexed from, address indexed to); /** @notice Emitted when a scope has an operator added @@ -341,7 +341,7 @@ interface IPatchworkProtocol { @param actor The address responsible for the action @param operator The new operator's address */ - event ScopeAddOperator(string indexed scopeName, address indexed actor, address indexed operator); + event ScopeAddOperator(string scopeName, address indexed actor, address indexed operator); /** @notice Emitted when a scope has an operator removed @@ -349,7 +349,7 @@ interface IPatchworkProtocol { @param actor The address responsible for the action @param operator The operator's address being removed */ - event ScopeRemoveOperator(string indexed scopeName, address indexed actor, address indexed operator); + event ScopeRemoveOperator(string scopeName, address indexed actor, address indexed operator); /** @notice Emitted when a scope's rules are changed @@ -359,7 +359,7 @@ interface IPatchworkProtocol { @param allowUserAssign Indicates whether user assignments are allowed @param requireWhitelist Indicates whether a whitelist is required */ - event ScopeRuleChange(string indexed scopeName, address indexed actor, bool allowUserPatch, bool allowUserAssign, bool requireWhitelist); + event ScopeRuleChange(string scopeName, address indexed actor, bool allowUserPatch, bool allowUserAssign, bool requireWhitelist); /** @notice Emitted when a scope has an address added to the whitelist @@ -367,7 +367,7 @@ interface IPatchworkProtocol { @param actor The address responsible for the action @param addr The address being added to the whitelist */ - event ScopeWhitelistAdd(string indexed scopeName, address indexed actor, address indexed addr); + event ScopeWhitelistAdd(string scopeName, address indexed actor, address indexed addr); /** @notice Emitted when a scope has an address removed from the whitelist @@ -375,7 +375,7 @@ interface IPatchworkProtocol { @param actor The address responsible for the action @param addr The address being removed from the whitelist */ - event ScopeWhitelistRemove(string indexed scopeName, address indexed actor, address indexed addr); + event ScopeWhitelistRemove(string scopeName, address indexed actor, address indexed addr); /** @notice Claim a scope From 0272bbb2192a3981e159e4f182e3aeb5c07b6f12 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Wed, 1 Nov 2023 12:55:48 -0700 Subject: [PATCH 11/63] Relation types (#32) * WIP renaming current fragment to single * Begin splitting single/multi assignable logic * Rename unassignNFT to unassignSingleNFT * Changing signatures to work WIP * more sigs * multi data structure starting * Multi WIP * First multi contract * basic multi working * recover gas on unassign * Made unique assignment checking global * Allow cross user assignments for multi * Multi assign and unassign permissions and test coverage * comments * Assignment count and paginated getter * Failing test for cross scope case - needs some additions to enable * Target scope multi permissions and cross scope support * Added by field so a multi assignable fragment can whitelist certain actors for specific actions * small linter fix * Assign permission principles * Refactoring to common logic for unassign * coverage * coverage * coverage * Coverage complete --- src/IPatchworkAssignableNFT.sol | 41 +--- src/IPatchworkMultiAssignableNFT.sol | 45 ++++ src/IPatchworkProtocol.sol | 59 +++-- src/IPatchworkSingleAssignableNFT.sol | 46 ++++ src/PatchworkFragmentMulti.sol | 141 ++++++++++++ ...agment.sol => PatchworkFragmentSingle.sol} | 21 +- src/PatchworkProtocol.sol | 137 ++++++++---- src/sampleNFTs/TestFragmentLiteRefNFT.sol | 13 +- src/sampleNFTs/TestMultiFragmentNFT.sol | 66 ++++++ test/PatchworkFragmentMulti.t.sol | 202 ++++++++++++++++++ ...nt.t.sol => PatchworkFragmentSingle.t.sol} | 3 +- test/PatchworkProtocol.t.sol | 107 ++++++++-- 12 files changed, 758 insertions(+), 123 deletions(-) create mode 100644 src/IPatchworkMultiAssignableNFT.sol create mode 100644 src/IPatchworkSingleAssignableNFT.sol create mode 100644 src/PatchworkFragmentMulti.sol rename src/{PatchworkFragment.sol => PatchworkFragmentSingle.sol} (81%) create mode 100644 src/sampleNFTs/TestMultiFragmentNFT.sol create mode 100644 test/PatchworkFragmentMulti.t.sol rename test/{PatchworkFragment.t.sol => PatchworkFragmentSingle.t.sol} (94%) diff --git a/src/IPatchworkAssignableNFT.sol b/src/IPatchworkAssignableNFT.sol index 903f67e..af6ddb1 100644 --- a/src/IPatchworkAssignableNFT.sol +++ b/src/IPatchworkAssignableNFT.sol @@ -7,6 +7,7 @@ pragma solidity ^0.8.13; @notice Interface for contracts supporting Patchwork assignment */ interface IPatchworkAssignableNFT { + /** @notice Get the scope this NFT claims to belong to @return string the name of the scope @@ -22,39 +23,15 @@ interface IPatchworkAssignableNFT { function assign(uint256 ourTokenId, address to, uint256 tokenId) external; /** - @notice Unassigns a token - @param ourTokenId ID of our token - */ - function unassign(uint256 ourTokenId) external; - - /** - @notice Returns the address and token ID that our token is assigned to - @param ourTokenId ID of our token - @return address the address this is assigned to - @return uint256 the tokenId this is assigned to - */ - function getAssignedTo(uint256 ourTokenId) external view returns (address, uint256); - - /** - @notice Returns the underlying stored owner of a token ignoring current assignment - @param ourTokenId ID of our token - @return address address of the owner - */ - function unassignedOwnerOf(uint256 ourTokenId) external view returns (address); - - /** - @notice Sends events for a token when the assigned-to token has been transferred - @param from Sender address - @param to Recipient address - @param tokenId ID of the token - */ - function onAssignedTransfer(address from, address to, uint256 tokenId) external; - - /** - @notice Updates the real underlying ownership of a token in storage (if different from current) - @param tokenId ID of the token + @notice Checks permissions for assignment + @param ourTokenId the tokenID to assign + @param target the address of the target + @param targetTokenId the tokenID of the target + @param targetOwner the ownerOf of the target + @param by the account invoking the assignment to Patchwork Protocol + @param scopeName the scope name of the contract to assign to */ - function updateOwnership(uint256 tokenId) external; + function allowAssignment(uint256 ourTokenId, address target, uint256 targetTokenId, address targetOwner, address by, string memory scopeName) external returns (bool); /** @notice A deliberately incompatible function to block implementing both assignable and patch diff --git a/src/IPatchworkMultiAssignableNFT.sol b/src/IPatchworkMultiAssignableNFT.sol new file mode 100644 index 0000000..6b60d5f --- /dev/null +++ b/src/IPatchworkMultiAssignableNFT.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "./IPatchworkAssignableNFT.sol"; + +/** +@title Patchwork Protocol Assignable NFT Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting Patchwork assignment +*/ +interface IPatchworkMultiAssignableNFT is IPatchworkAssignableNFT { + + struct Assignment { + address tokenAddr; /// The address of the external NFT contract. + uint256 tokenId; /// The ID of the token in the external NFT contract. + } + + /** + @notice Checks if this fragment is assigned to a target + @param ourTokenId the tokenId of the fragment + @param target the address of the target + @param targetTokenId the tokenId of the target + */ + function isAssignedTo(uint256 ourTokenId, address target, uint256 targetTokenId) external returns (bool); + + /** + @notice Unassigns a token + @param ourTokenId tokenId of our fragment + */ + function unassign(uint256 ourTokenId, address target, uint256 targetTokenId) external; + + /** + @notice Counts the number of unique assignments this token has + @param tokenId tokenId of our fragment + */ + function getAssignmentCount(uint256 tokenId) external returns (uint256); + + /** + @notice Gets assignments for a fragment + @param tokenId tokenId of our fragment + @param offset the page offset + @param count the maximum numer of entries to return + */ + function getAssignments(uint256 tokenId, uint256 offset, uint256 count) external returns (Assignment[] memory); +} \ No newline at end of file diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index 2771baf..3dec7a8 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -93,21 +93,12 @@ interface IPatchworkProtocol { error FragmentAlreadyAssigned(address addr, uint256 tokenId); /** - @notice The fragment with the provided ID at the given address is already assigned in the scope - @param scopeName Name of the scope - @param addr Address of the fragment - @param tokenId ID of the fragment - */ - error FragmentAlreadyAssignedInScope(string scopeName, address addr, uint256 tokenId); - - /** - @notice The reference was not found in the scope for the given fragment and target - @param scopeName Name of the scope + @notice The reference was not found for the given fragment and target @param target Address of the target token @param fragment Address of the fragment @param tokenId ID of the fragment */ - error RefNotFoundInScope(string scopeName, address target, address fragment, uint256 tokenId); + error RefNotFound(address target, address fragment, uint256 tokenId); /** @notice The fragment with the provided ID at the given address is not assigned @@ -116,6 +107,15 @@ interface IPatchworkProtocol { */ error FragmentNotAssigned(address addr, uint256 tokenId); + /** + @notice The fragment with the provided ID at the given address is not assigned to the target + @param addr Address of the fragment + @param tokenId ID of the fragment + @param targetAddress Address of the target + @param targetTokenId ID of the target + */ + error FragmentNotAssignedToTarget(address addr, uint256 tokenId, address targetAddress, uint256 targetTokenId); + /** @notice The fragment at the given address is already registered @param addr Address of the registered fragment @@ -205,6 +205,11 @@ interface IPatchworkProtocol { */ error UnsupportedOperation(); + /** + @notice The contract is not supported + */ + error UnsupportedContract(); + /** @notice Represents a defined scope within the system @dev Contains details about the scope ownership, permissions, and mappings for references and assignments @@ -246,12 +251,6 @@ interface IPatchworkProtocol { */ mapping(address => bool) operators; - /** - @notice Mapped list of lightweight references within this scope - @dev A hash of liteRefAddr + reference provides uniqueness - */ - mapping(bytes32 => bool) liteRefs; - /** @notice Mapped whitelist of addresses that belong to this scope @dev Address mapped to a boolean indicating if it's whitelisted @@ -491,10 +490,30 @@ interface IPatchworkProtocol { /** @notice Unassign a NFT fragment from a target NFT - @param fragment The IPatchworkAssignableNFT address of the fragment NFT - @param fragmentTokenId The IPatchworkAssignableNFT token ID of the fragment NFT + @param fragment The IPatchworkSingleAssignableNFT address of the fragment NFT + @param fragmentTokenId The IPatchworkSingleAssignableNFT token ID of the fragment NFT + @dev reverts if fragment is not an IPatchworkSingleAssignableNFT + */ + function unassignSingleNFT(address fragment, uint fragmentTokenId) external; + + /** + @notice Unassigns a multi NFT relation + @param fragment The IPatchworMultiAssignableNFT address to unassign + @param fragmentTokenId The IPatchworkMultiAssignableNFT Token ID to unassign + @param target The IPatchworkLiteRef address which holds a reference to the fragment + @param targetTokenId The IPatchworkLiteRef Token ID which holds a reference to the fragment + @dev reverts if fragment is not an IPatchworkMultiAssignableNFT + */ + function unassignMultiNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external; + + /** + @notice Unassigns an NFT relation (single or multi) + @param fragment The IPatchworkAssignableNFT address to unassign + @param fragmentTokenId The IPatchworkAssignableNFT Token ID to unassign + @param target The IPatchworkLiteRef address which holds a reference to the fragment + @param targetTokenId The IPatchworkLiteRef Token ID which holds a reference to the fragment */ - function unassignNFT(address fragment, uint fragmentTokenId) external; + function unassignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external; /** @notice Apply transfer rules and actions of a specific token from one address to another diff --git a/src/IPatchworkSingleAssignableNFT.sol b/src/IPatchworkSingleAssignableNFT.sol new file mode 100644 index 0000000..98cb675 --- /dev/null +++ b/src/IPatchworkSingleAssignableNFT.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "./IPatchworkAssignableNFT.sol"; + +/** +@title Patchwork Protocol Assignable NFT Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting Patchwork assignment +*/ +interface IPatchworkSingleAssignableNFT is IPatchworkAssignableNFT { + /** + @notice Unassigns a token + @param ourTokenId ID of our token + */ + function unassign(uint256 ourTokenId) external; + + /** + @notice Returns the address and token ID that our token is assigned to + @param ourTokenId ID of our token + @return address the address this is assigned to + @return uint256 the tokenId this is assigned to + */ + function getAssignedTo(uint256 ourTokenId) external view returns (address, uint256); + + /** + @notice Returns the underlying stored owner of a token ignoring current assignment + @param ourTokenId ID of our token + @return address address of the owner + */ + function unassignedOwnerOf(uint256 ourTokenId) external view returns (address); + + /** + @notice Sends events for a token when the assigned-to token has been transferred + @param from Sender address + @param to Recipient address + @param tokenId ID of the token + */ + function onAssignedTransfer(address from, address to, uint256 tokenId) external; + + /** + @notice Updates the real underlying ownership of a token in storage (if different from current) + @param tokenId ID of the token + */ + function updateOwnership(uint256 tokenId) external; +} \ No newline at end of file diff --git a/src/PatchworkFragmentMulti.sol b/src/PatchworkFragmentMulti.sol new file mode 100644 index 0000000..5c9868f --- /dev/null +++ b/src/PatchworkFragmentMulti.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "./PatchworkNFT.sol"; +import "./IPatchworkMultiAssignableNFT.sol"; + +/** +@title PatchworkFragmentMulti +@dev base implementation of a Single-relation Fragment is IPatchworkAssignableNFT +*/ +abstract contract PatchworkFragmentMulti is PatchworkNFT, IPatchworkMultiAssignableNFT { + + struct AssignmentStorage { + mapping(bytes32 => uint256) index; + Assignment[] assignments; + } + + // Only presence-checking is available here + + /// A mapping from token IDs in this contract to their assignments. + mapping(uint256 => AssignmentStorage) internal _assignmentStorage; + + /** + @dev See {IPatchworkNFT-getScopeName} + */ + function getScopeName() public view virtual override (IPatchworkAssignableNFT, PatchworkNFT) returns (string memory) { + return _scopeName; + } + + /** + @dev See {IERC165-supportsInterface} + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(IPatchworkAssignableNFT).interfaceId || + interfaceID == type(IPatchworkMultiAssignableNFT).interfaceId || + super.supportsInterface(interfaceID); + } + + /** + @dev See {IPatchworkAssignableNFT-assign} + */ + function assign(uint256 ourTokenId, address to, uint256 tokenId) public virtual mustHaveTokenWriteAuth(ourTokenId) { + AssignmentStorage storage store = _assignmentStorage[ourTokenId]; + (bool present,, bytes32 targetHash) = _assignmentIndexOf(store, to, tokenId); + if (present) { + revert IPatchworkProtocol.FragmentAlreadyAssigned(address(this), ourTokenId); + } + Assignment[] storage assignments = store.assignments; + uint256 idx = assignments.length; + assignments.push(Assignment(to, tokenId)); + store.index[targetHash] = idx; + } + + /** + @notice Unassigns a token + @param ourTokenId ID of our token + */ + function unassign(uint256 ourTokenId, address target, uint256 targetTokenId) public virtual mustHaveTokenWriteAuth(ourTokenId) { + AssignmentStorage storage store = _assignmentStorage[ourTokenId]; + (bool present, uint256 index, bytes32 targetHash) = _assignmentIndexOf(store, target, targetTokenId); + if (!present) { + revert IPatchworkProtocol.FragmentNotAssigned(address(this), ourTokenId); + } + Assignment[] storage assignments = store.assignments; + if (assignments.length > 1) { + // move the last element of the array into this index + assignments[index] = assignments[assignments.length-1]; + } + // shorten the array by 1 + assignments.pop(); + // delete the index + delete store.index[targetHash]; + } + + function isAssignedTo(uint256 ourTokenId, address target, uint256 targetTokenId) public view virtual returns (bool) { + (bool present,,) = _assignmentIndexOf(_assignmentStorage[ourTokenId], target, targetTokenId); + return present; + } + + function _assignmentIndexOf(AssignmentStorage storage store, address target, uint256 targetTokenId) internal view returns (bool present, uint256 index, bytes32 targetHash) { + targetHash = keccak256(abi.encodePacked(target, targetTokenId)); + uint256 storageIndex = store.index[targetHash]; + Assignment[] storage assignments = store.assignments; + if (storageIndex == 0) { + // Either the first element or does not exist + if (assignments.length > 0) { + // there is an assignment of some kind - need to check if it's this one + if (assignments[0].tokenAddr == target && assignments[0].tokenId == targetTokenId) { + return (true, 0, targetHash); + } + } + } else { + // There is definitely an index to this. + return (true, storageIndex, targetHash); + } + return (false, 0, targetHash); + } + + + /** + @dev See {IPatchworkNFT-getAssignmentCount} + */ + function getAssignmentCount(uint256 tokenId) public view returns (uint256) { + return _assignmentStorage[tokenId].assignments.length; + } + + /** + @dev See {IPatchworkNFT-getAssignments} + */ + function getAssignments(uint256 tokenId, uint256 offset, uint256 count) external view returns (Assignment[] memory) { + AssignmentStorage storage store = _assignmentStorage[tokenId]; + Assignment[] storage assignments = store.assignments; + if (offset >= assignments.length) { + return new Assignment[](0); + } + // Determine the actual count of assignments to return + uint256 retCount = count; + if (offset + count > assignments.length) { + retCount = assignments.length - offset; + } + // Fetch assignments + Assignment[] memory page = new Assignment[](retCount); + for (uint256 i = 0; i < retCount; i++) { + page[i] = assignments[offset + i]; + } + return page; + } + + /** + @dev See {IPatchworAssignable-allowAssignment} + */ + function allowAssignment(uint256 /*ourTokenId*/, address /*target*/, uint256 /*targetTokenId*/, address /*targetOwner*/, address /*by*/, string memory /*scopeName*/) pure virtual public returns (bool) { + // By default allow multi assignments public + return true; + } + + /** + @dev See {IPatchworkNFT-patchworkCompatible_} + */ + function patchworkCompatible_() external pure returns (bytes2) {} +} \ No newline at end of file diff --git a/src/PatchworkFragment.sol b/src/PatchworkFragmentSingle.sol similarity index 81% rename from src/PatchworkFragment.sol rename to src/PatchworkFragmentSingle.sol index fd827d6..4b98a4d 100644 --- a/src/PatchworkFragment.sol +++ b/src/PatchworkFragmentSingle.sol @@ -2,13 +2,16 @@ pragma solidity ^0.8.13; import "./PatchworkNFT.sol"; -import "./IPatchworkAssignableNFT.sol"; +import "./IPatchworkSingleAssignableNFT.sol"; + +// TODO create a patch fragment implementation where ownership is weak for fragments +// TODO change the patchworkCompatible to make that work /** -@title PatchworkFragment -@dev base implementation of a Fragment is IPatchworkAssignableNFT +@title PatchworkFragmentSingle +@dev base implementation of a Single-relation Fragment is IPatchworkSingleAssignableNFT */ -abstract contract PatchworkFragment is PatchworkNFT, IPatchworkAssignableNFT { +abstract contract PatchworkFragmentSingle is PatchworkNFT, IPatchworkSingleAssignableNFT { /// Represents an assignment of a token from an external NFT contract to a token in this contract. struct Assignment { @@ -31,6 +34,7 @@ abstract contract PatchworkFragment is PatchworkNFT, IPatchworkAssignableNFT { */ function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { return interfaceID == type(IPatchworkAssignableNFT).interfaceId || + interfaceID == type(IPatchworkSingleAssignableNFT).interfaceId || super.supportsInterface(interfaceID); } @@ -57,6 +61,15 @@ abstract contract PatchworkFragment is PatchworkNFT, IPatchworkAssignableNFT { emit Unlocked(tokenId); } + /** + @dev See {IPatchworAssignable-allowAssignment} + */ + function allowAssignment(uint256 ourTokenId, address /*target*/, uint256 /*targetTokenId*/, address targetOwner, address /*by*/, string memory /*scopeName*/) public view returns (bool) { + // By default only allow single assignments to be to the same owner as the target + // Warning - Changing this without changing the other ownership logic in this contract to reflect this will make ownership inconsistent + return targetOwner == ownerOf(ourTokenId); + } + /** @dev See {IPatchworkAssignableNFT-updateOwnership} */ diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index e1449a8..04ee4de 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.13; import "./IPatchworkNFT.sol"; -import "./IPatchworkAssignableNFT.sol"; +import "./IPatchworkSingleAssignableNFT.sol"; +import "./IPatchworkMultiAssignableNFT.sol"; import "./IPatchworkLiteRef.sol"; import "./IPatchworkPatch.sol"; import "./IPatchworkAccountPatch.sol"; @@ -19,6 +20,12 @@ contract PatchworkProtocol is IPatchworkProtocol { /// Scopes mapping(string => Scope) private _scopes; + /** + @notice unique references + @dev A hash of target + targetTokenId + literef provides uniqueness + */ + mapping(bytes32 => bool) _liteRefs; + /** @dev See {IPatchworkProtocol-claimScope} */ @@ -235,27 +242,29 @@ contract PatchworkProtocol is IPatchworkProtocol { if (_isLocked(fragment, fragmentTokenId)) { revert Locked(fragment, fragmentTokenId); } - // Use the fragment's scope for permissions, target already has to have fragment registered to be assignable - string memory scopeName = assignableNFT.getScopeName(); - Scope storage scope = _mustHaveScope(scopeName); - _mustBeWhitelisted(scopeName, scope, fragment); - if (scope.owner == msg.sender || scope.operators[msg.sender]) { - // Fragment and target must be same owner - if (IERC721(fragment).ownerOf(fragmentTokenId) != targetOwner) { - revert NotAuthorized(msg.sender); - } - } else if (scope.allowUserAssign) { - // If allowUserAssign is set for this scope, the sender must own both fragment and target - if (IERC721(fragment).ownerOf(fragmentTokenId) != msg.sender) { - revert NotAuthorized(msg.sender); - } + // Use the target's scope for general permission and check the fragment for detailed permissions + string memory targetScopeName = IPatchworkNFT(target).getScopeName(); + Scope storage targetScope = _mustHaveScope(targetScopeName); + _mustBeWhitelisted(targetScopeName, targetScope, target); + { + // Whitelist check, these variables do not need to stay in the function level stack + string memory fragmentScopeName = assignableNFT.getScopeName(); + Scope storage fragmentScope = _mustHaveScope(fragmentScopeName); + _mustBeWhitelisted(fragmentScopeName, fragmentScope, fragment); + } + if (targetScope.owner == msg.sender || targetScope.operators[msg.sender]) { + // all good + } else if (targetScope.allowUserAssign) { + // msg.sender must own the target if (targetOwner != msg.sender) { revert NotAuthorized(msg.sender); } - // continue } else { revert NotAuthorized(msg.sender); } + if (!IPatchworkAssignableNFT(fragment).allowAssignment(fragmentTokenId, target, targetTokenId, targetOwner, msg.sender, targetScopeName)) { + revert NotAuthorized(msg.sender); + } bytes32 targetRef; // reduce stack to stay under limit address _target = target; @@ -263,20 +272,21 @@ contract PatchworkProtocol is IPatchworkProtocol { address _fragment = fragment; uint256 _fragmentTokenId = fragmentTokenId; (uint64 ref, bool redacted) = IPatchworkLiteRef(_target).getLiteReference(_fragment, _fragmentTokenId); - targetRef = keccak256(abi.encodePacked(_target, ref)); + // targetRef is a compound key (targetAddr+targetTokenID+fragmentAddr+fragmentTokenID) - blocks duplicate assignments + targetRef = keccak256(abi.encodePacked(_target, _targetTokenId, ref)); if (ref == 0) { revert FragmentUnregistered(address(_fragment)); } if (redacted) { revert FragmentRedacted(address(_fragment)); } - if (scope.liteRefs[targetRef]) { - revert FragmentAlreadyAssignedInScope(scopeName, address(_fragment), _fragmentTokenId); + if (_liteRefs[targetRef]) { + revert FragmentAlreadyAssigned(address(_fragment), _fragmentTokenId); } // call assign on the fragment assignableNFT.assign(_fragmentTokenId, _target, _targetTokenId); - // add to our storage of scope->target assignments - scope.liteRefs[targetRef] = true; + // add to our storage of assignments + _liteRefs[targetRef] = true; emit Assign(targetOwner, _fragment, _fragmentTokenId, _target, _targetTokenId); return ref; } @@ -284,35 +294,76 @@ contract PatchworkProtocol is IPatchworkProtocol { /** @dev See {IPatchworkProtocol-unassignNFT} */ - function unassignNFT(address fragment, uint fragmentTokenId) public mustNotBeFrozen(fragment, fragmentTokenId) { - IPatchworkAssignableNFT assignableNFT = IPatchworkAssignableNFT(fragment); + function unassignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public { + if (IERC165(fragment).supportsInterface(type(IPatchworkMultiAssignableNFT).interfaceId)) { + unassignMultiNFT(fragment, fragmentTokenId, target, targetTokenId); + } else if (IERC165(fragment).supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)) { + (address _target, uint256 _targetTokenId) = IPatchworkSingleAssignableNFT(fragment).getAssignedTo(fragmentTokenId); + if (target != _target || _targetTokenId != targetTokenId) { + revert FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); + } + unassignSingleNFT(fragment, fragmentTokenId); + } else { + revert UnsupportedContract(); + } + } + + /** + @dev See {IPatchworkProtocol-unassignMultiNFT} + */ + function unassignMultiNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { + IPatchworkMultiAssignableNFT assignable = IPatchworkMultiAssignableNFT(fragment); + string memory scopeName = assignable.getScopeName(); + if (!assignable.isAssignedTo(fragmentTokenId, target, targetTokenId)) { + revert FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); + } + _doUnassign(fragment, fragmentTokenId, target, targetTokenId, scopeName); + assignable.unassign(fragmentTokenId, target, targetTokenId); + } + + /** + @dev See {IPatchworkProtocol-unassignNFT} + */ + function unassignSingleNFT(address fragment, uint fragmentTokenId) public mustNotBeFrozen(fragment, fragmentTokenId) { + IPatchworkSingleAssignableNFT assignableNFT = IPatchworkSingleAssignableNFT(fragment); string memory scopeName = assignableNFT.getScopeName(); + (address target, uint256 targetTokenId) = assignableNFT.getAssignedTo(fragmentTokenId); + if (target == address(0)) { + revert FragmentNotAssigned(fragment, fragmentTokenId); + } + _doUnassign(fragment, fragmentTokenId, target, targetTokenId, scopeName); + assignableNFT.unassign(fragmentTokenId); + } + + /** + @notice Performs unassignment of an IPatchworkAssignableNFT to an IPatchworkLiteRef + @param fragment the IPatchworkAssignableNFT's address + @param fragmentTokenId the IPatchworkAssignableNFT's tokenId + @param target the IPatchworkLiteRef target's address + @param targetTokenId the IPatchworkLiteRef target's tokenId + @param scopeName the name of the assignable's scope + */ + function _doUnassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, string memory scopeName) private { Scope storage scope = _mustHaveScope(scopeName); if (scope.owner == msg.sender || scope.operators[msg.sender]) { // continue } else if (scope.allowUserAssign) { - // If allowUserAssign is set for this scope, the sender must own both fragment - if (IERC721(fragment).ownerOf(fragmentTokenId) != msg.sender) { + if (IERC721(target).ownerOf(targetTokenId) != msg.sender) { revert NotAuthorized(msg.sender); } // continue } else { revert NotAuthorized(msg.sender); } - (address target, uint256 targetTokenId) = IPatchworkAssignableNFT(fragment).getAssignedTo(fragmentTokenId); - if (target == address(0)) { - revert FragmentNotAssigned(fragment, fragmentTokenId); - } - assignableNFT.unassign(fragmentTokenId); (uint64 ref, ) = IPatchworkLiteRef(target).getLiteReference(fragment, fragmentTokenId); if (ref == 0) { revert FragmentUnregistered(address(fragment)); } - bytes32 targetRef = keccak256(abi.encodePacked(target, ref)); - if (!scope.liteRefs[targetRef]) { - revert RefNotFoundInScope(scopeName, target, fragment, fragmentTokenId); + bytes32 targetRef = keccak256(abi.encodePacked(target, targetTokenId, ref)); + if (!_liteRefs[targetRef]) { + revert RefNotFound(target, fragment, fragmentTokenId); } - scope.liteRefs[targetRef] = false; + delete _liteRefs[targetRef]; IPatchworkLiteRef(target).removeReference(targetTokenId, ref); emit Unassign(IERC721(target).ownerOf(targetTokenId), fragment, fragmentTokenId, target, targetTokenId); } @@ -322,8 +373,8 @@ contract PatchworkProtocol is IPatchworkProtocol { */ function applyTransfer(address from, address to, uint256 tokenId) public { address nft = msg.sender; - if (IERC165(nft).supportsInterface(type(IPatchworkAssignableNFT).interfaceId)) { - IPatchworkAssignableNFT assignableNFT = IPatchworkAssignableNFT(nft); + if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)) { + IPatchworkSingleAssignableNFT assignableNFT = IPatchworkSingleAssignableNFT(nft); (address addr,) = assignableNFT.getAssignedTo(tokenId); if (addr != address(0)) { revert TransferBlockedByAssignment(nft, tokenId); @@ -349,15 +400,15 @@ contract PatchworkProtocol is IPatchworkProtocol { } function _applyAssignedTransfer(address nft, address from, address to, uint256 tokenId, address assignedToNFT_, uint256 assignedToTokenId_) private { - if (!IERC165(nft).supportsInterface(type(IPatchworkAssignableNFT).interfaceId)) { + if (!IERC165(nft).supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)) { revert NotPatchworkAssignable(nft); } - (address assignedToNFT, uint256 assignedToTokenId) = IPatchworkAssignableNFT(nft).getAssignedTo(tokenId); + (address assignedToNFT, uint256 assignedToTokenId) = IPatchworkSingleAssignableNFT(nft).getAssignedTo(tokenId); // 2-way Check the assignment to prevent spoofing if (assignedToNFT_ != assignedToNFT || assignedToTokenId_ != assignedToTokenId) { revert DataIntegrityError(assignedToNFT_, assignedToTokenId_, assignedToNFT, assignedToTokenId); } - IPatchworkAssignableNFT(nft).onAssignedTransfer(from, to, tokenId); + IPatchworkSingleAssignableNFT(nft).onAssignedTransfer(from, to, tokenId); if (IERC165(nft).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { address nft_ = nft; // local variable prevents optimizer stack issue in v0.8.18 IPatchworkLiteRef liteRefNFT = IPatchworkLiteRef(nft); @@ -383,8 +434,8 @@ contract PatchworkProtocol is IPatchworkProtocol { } } } - if (IERC165(nft).supportsInterface(type(IPatchworkAssignableNFT).interfaceId)) { - IPatchworkAssignableNFT(nft).updateOwnership(tokenId); + if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)) { + IPatchworkSingleAssignableNFT(nft).updateOwnership(tokenId); } else if (IERC165(nft).supportsInterface(type(IPatchworkPatch).interfaceId)) { IPatchworkPatch(nft).updateOwnership(tokenId); } @@ -461,8 +512,8 @@ contract PatchworkProtocol is IPatchworkProtocol { if (IPatchworkNFT(nft).frozen(tokenId)) { return true; } - if (IERC165(nft).supportsInterface(type(IPatchworkAssignableNFT).interfaceId)) { - (address assignedAddr, uint256 assignedTokenId) = IPatchworkAssignableNFT(nft).getAssignedTo(tokenId); + if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)) { + (address assignedAddr, uint256 assignedTokenId) = IPatchworkSingleAssignableNFT(nft).getAssignedTo(tokenId); if (assignedAddr != address(0)) { return _isFrozen(assignedAddr, assignedTokenId); } diff --git a/src/sampleNFTs/TestFragmentLiteRefNFT.sol b/src/sampleNFTs/TestFragmentLiteRefNFT.sol index 112d1ad..d8daf83 100644 --- a/src/sampleNFTs/TestFragmentLiteRefNFT.sol +++ b/src/sampleNFTs/TestFragmentLiteRefNFT.sol @@ -9,7 +9,7 @@ pragma solidity ^0.8.13; Has metadata as defined in totem-metadata.json */ -import "../PatchworkFragment.sol"; +import "../PatchworkFragmentSingle.sol"; import "../PatchworkLiteRef.sol"; enum FragmentType { @@ -26,7 +26,7 @@ struct TestFragmentLiteRefNFTMetadata { string name; } -contract TestFragmentLiteRefNFT is PatchworkFragment, PatchworkLiteRef { +contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef { uint256 _nextTokenId; bool _testLockOverride; @@ -39,9 +39,9 @@ contract TestFragmentLiteRefNFT is PatchworkFragment, PatchworkLiteRef { } // ERC-165 - function supportsInterface(bytes4 interfaceID) public view virtual override(PatchworkFragment, PatchworkLiteRef) returns (bool) { + function supportsInterface(bytes4 interfaceID) public view virtual override(PatchworkFragmentSingle, PatchworkLiteRef) returns (bool) { return PatchworkLiteRef.supportsInterface(interfaceID) || - PatchworkFragment.supportsInterface(interfaceID); + PatchworkFragmentSingle.supportsInterface(interfaceID); } function mint(address to) external returns (uint256 tokenId) { @@ -236,4 +236,9 @@ contract TestFragmentLiteRefNFT is PatchworkFragment, PatchworkLiteRef { } return super.getAssignedTo(ourTokenId); } + + function setScopeName(string memory scopeName) public { + // For testing only + _scopeName = scopeName; + } } \ No newline at end of file diff --git a/src/sampleNFTs/TestMultiFragmentNFT.sol b/src/sampleNFTs/TestMultiFragmentNFT.sol new file mode 100644 index 0000000..2268643 --- /dev/null +++ b/src/sampleNFTs/TestMultiFragmentNFT.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "../PatchworkFragmentMulti.sol"; +import "../PatchworkLiteRef.sol"; + +struct TestMultiFragmentNFTMetadata { + uint8 nothing; +} + +contract TestMultiFragmentNFT is PatchworkFragmentMulti { + uint256 _nextTokenId; + + constructor (address _manager) PatchworkNFT("testscope", "TestMultiFragmentNFT", "TFLR", msg.sender, _manager) { + } + + function mint(address to) external returns (uint256 tokenId) { + tokenId = _nextTokenId; + _nextTokenId++; + _safeMint(to, tokenId); + _metadataStorage[tokenId] = new uint256[](1); + } + + function setScopeName(string memory scopeName) public { + // For testing only + _scopeName = scopeName; + } + + function schemaURI() pure external returns (string memory) { + return "https://mything/my-fragment-metadata.json"; + } + + function imageURI(uint256 _tokenId) pure external returns (string memory) { + return string(abi.encodePacked("https://mything/fragment-", _tokenId)); + } + + /* + Hard coded prototype schema is: + slot 0 offset 0 = nothing + */ + function schema() pure external returns (MetadataSchema memory) { + MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](8); + entries[0] = MetadataSchemaEntry(0, 0, FieldType.UINT8, 0, FieldVisibility.PUBLIC, 0, 0, "nothing"); + return MetadataSchema(1, entries); + } + + function packMetadata(TestMultiFragmentNFTMetadata memory data) public pure returns (uint256[] memory slots) { + slots = new uint256[](1); + slots[0] = uint256(data.nothing); + return slots; + } + + function storeMetadata(uint256 _tokenId, TestMultiFragmentNFTMetadata memory data) public { + require(_checkTokenWriteAuth(_tokenId), "not authorized"); + _metadataStorage[_tokenId] = packMetadata(data); + } + + function unpackMetadata(uint256[] memory slots) public pure returns (TestMultiFragmentNFTMetadata memory data) { + data.nothing = uint8(slots[0]); + return data; + } + + function loadMetadata(uint256 _tokenId) public view returns (TestMultiFragmentNFTMetadata memory data) { + return unpackMetadata(_metadataStorage[_tokenId]); + } +} \ No newline at end of file diff --git a/test/PatchworkFragmentMulti.t.sol b/test/PatchworkFragmentMulti.t.sol new file mode 100644 index 0000000..370254b --- /dev/null +++ b/test/PatchworkFragmentMulti.t.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "../src/PatchworkProtocol.sol"; +import "../src/sampleNFTs/TestFragmentLiteRefNFT.sol"; +import "../src/sampleNFTs/TestBaseNFT.sol"; +import "../src/sampleNFTs/TestMultiFragmentNFT.sol"; + +contract PatchworkFragmentMultiTest is Test { + PatchworkProtocol _prot; + TestFragmentLiteRefNFT _testFragmentLiteRefNFT; + TestMultiFragmentNFT _testMultiNFT; + + string _scopeName; + address _defaultUser; + address _scopeOwner; + address _patchworkOwner; + address _userAddress; + address _user2Address; + + function setUp() public { + _defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; + _patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; + _userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; + _user2Address = address(550001); + _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; + + vm.prank(_patchworkOwner); + _prot = new PatchworkProtocol(); + _scopeName = "testscope"; + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + + _testFragmentLiteRefNFT = new TestFragmentLiteRefNFT(address(_prot)); + _testMultiNFT = new TestMultiFragmentNFT(address(_prot)); + + vm.stopPrank(); + } + + function testScopeName() public { + assertEq(_scopeName, _testMultiNFT.getScopeName()); + } + + function testSupportsInterface() public { + assertTrue(_testMultiNFT.supportsInterface(type(IERC165).interfaceId)); + assertTrue(_testMultiNFT.supportsInterface(type(IERC721).interfaceId)); + assertTrue(_testMultiNFT.supportsInterface(type(IERC4906).interfaceId)); + assertTrue(_testMultiNFT.supportsInterface(type(IERC5192).interfaceId)); + assertTrue(_testMultiNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); + assertTrue(_testMultiNFT.supportsInterface(type(IPatchworkAssignableNFT).interfaceId)); + assertTrue(_testMultiNFT.supportsInterface(type(IPatchworkMultiAssignableNFT).interfaceId)); + } + + function testMultiAssign() public { + vm.startPrank(_scopeOwner); + uint256 m1 = _testMultiNFT.mint(_user2Address); + uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 lr2 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 lr3 = _testFragmentLiteRefNFT.mint(_userAddress); + // must be registered + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testMultiNFT))); + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + // happy path + _testFragmentLiteRefNFT.registerReferenceAddress(address(_testMultiNFT)); + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr1)); + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr2)); + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr3); + assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr3)); + assertEq(_testMultiNFT.ownerOf(m1), _user2Address); + // don't allow duplicate + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(_testMultiNFT), m1)); + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + // Don't allow duplicate (direct on NFT contract) + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(_testMultiNFT), m1)); + _testMultiNFT.assign(m1, address(_testFragmentLiteRefNFT), lr2); + // don't allow either owner or random user to unassign + vm.stopPrank(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); + _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + vm.prank(_user2Address); + _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, address(500))); + vm.prank(address(500)); + _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + vm.startPrank(_scopeOwner); + // test unassign + _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + assertFalse(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr2)); + _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + assertFalse(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr1)); + _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr3); + assertFalse(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr3)); + // not assigned + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssignedToTarget.selector, address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2)); + _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + // not assigned (direct to contract) + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssigned.selector, address(_testMultiNFT), m1)); + _testMultiNFT.unassign(m1, address(_testFragmentLiteRefNFT), lr2); + // test reassign + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr2)); + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr3); + assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr3)); + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr1)); + } + + function testMultiAssignUserAssign() public { + vm.startPrank(_scopeOwner); + // Enable user assign + _prot.setScopeRules(_scopeName, false, true, false); + uint256 m1 = _testMultiNFT.mint(_user2Address); + uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 lr2 = _testFragmentLiteRefNFT.mint(_userAddress); + // must be registered + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testMultiNFT))); + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + // happy path + _testFragmentLiteRefNFT.registerReferenceAddress(address(_testMultiNFT)); + // as scope owner, should not revert. Only as user if they don't match, but they should work if they match. + // should revert + vm.stopPrank(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + vm.prank(_user2Address); + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + // target owner should be able to assign to their target as well as the scope owner + vm.prank(_userAddress); + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + vm.prank(_scopeOwner); + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + vm.prank(_scopeOwner); + uint256 m2 = _testMultiNFT.mint(_userAddress); + // This should also work because both are owned by the same user + vm.prank(_userAddress); + _prot.assignNFT(address(_testMultiNFT), m2, address(_testFragmentLiteRefNFT), lr2); + } + + function testGetAssignments() public { + vm.startPrank(_scopeOwner); + uint256 m1 = _testMultiNFT.mint(_user2Address); + _testFragmentLiteRefNFT.registerReferenceAddress(address(_testMultiNFT)); + uint256[] memory liteRefIds = new uint256[](20); + for (uint256 i = 0; i < liteRefIds.length; i++) { + liteRefIds[i] = _testFragmentLiteRefNFT.mint(_userAddress); + _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), liteRefIds[i]); + } + assertEq(20, _testMultiNFT.getAssignmentCount(m1)); + IPatchworkMultiAssignableNFT.Assignment[] memory page1 = _testMultiNFT.getAssignments(m1, 0, 8); + IPatchworkMultiAssignableNFT.Assignment[] memory page2 = _testMultiNFT.getAssignments(m1, 8, 8); + IPatchworkMultiAssignableNFT.Assignment[] memory page3 = _testMultiNFT.getAssignments(m1, 16, 8); + IPatchworkMultiAssignableNFT.Assignment[] memory page4 = _testMultiNFT.getAssignments(m1, 20, 8); + IPatchworkMultiAssignableNFT.Assignment[] memory page5 = _testMultiNFT.getAssignments(m1, 100, 8); + assertEq(8, page1.length); + assertEq(8, page2.length); + assertEq(4, page3.length); + assertEq(0, page4.length); + assertEq(0, page5.length); + assertEq(page1[0].tokenAddr, address(_testFragmentLiteRefNFT)); + assertEq(page1[0].tokenId, liteRefIds[0]); + assertEq(page2[0].tokenAddr, address(_testFragmentLiteRefNFT)); + assertEq(page2[0].tokenId, liteRefIds[8]); + assertEq(page3[0].tokenAddr, address(_testFragmentLiteRefNFT)); + assertEq(page3[0].tokenId, liteRefIds[16]); + // Check non existant + IPatchworkMultiAssignableNFT.Assignment[] memory np1 = _testMultiNFT.getAssignments(11902381, 100, 8); + assertEq(0, np1.length); + } + + function testCrossScopeMulti() public { + string memory publicScope = "publicmulti"; + address publicScopeOwner = address(129837123); + vm.startPrank(publicScopeOwner); + TestMultiFragmentNFT multi = new TestMultiFragmentNFT(address(_prot)); + multi.setScopeName(publicScope); + _prot.claimScope(publicScope); + _prot.setScopeRules(publicScope, false, false, true); + _prot.addWhitelist(publicScope, address(multi)); + uint256 m1 = multi.mint(publicScopeOwner); + vm.stopPrank(); + // mow we have a multi fragment in "publicmulti" scope and another scope wants to use it - both require whitelisting + vm.startPrank(_scopeOwner); + _prot.setScopeRules(_scopeName, false, false, true); + _prot.addWhitelist(_scopeName, address(_testFragmentLiteRefNFT)); + _testFragmentLiteRefNFT.registerReferenceAddress(address(multi)); + uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress); + _prot.assignNFT(address(multi), m1, address(_testFragmentLiteRefNFT), lr1); + } + + function testPatchworkCompatible() public { + TestMultiFragmentNFT multi = new TestMultiFragmentNFT(address(_prot)); + multi.patchworkCompatible_(); + } + +} \ No newline at end of file diff --git a/test/PatchworkFragment.t.sol b/test/PatchworkFragmentSingle.t.sol similarity index 94% rename from test/PatchworkFragment.t.sol rename to test/PatchworkFragmentSingle.t.sol index 36431fb..7905e32 100644 --- a/test/PatchworkFragment.t.sol +++ b/test/PatchworkFragmentSingle.t.sol @@ -8,7 +8,7 @@ import "../src/PatchworkProtocol.sol"; import "../src/sampleNFTs/TestFragmentLiteRefNFT.sol"; import "../src/sampleNFTs/TestBaseNFT.sol"; -contract PatchworkFragmentTest is Test { +contract PatchworkFragmentSingleTest is Test { PatchworkProtocol _prot; TestFragmentLiteRefNFT _testFragmentLiteRefNFT; @@ -50,6 +50,7 @@ contract PatchworkFragmentTest is Test { assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IERC5192).interfaceId)); assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IPatchworkAssignableNFT).interfaceId)); + assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)); } function testOnAssignedTransferError() public { diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index 6ddd266..a09970f 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -9,6 +9,7 @@ import "../src/sampleNFTs/TestPatchLiteRefNFT.sol"; import "../src/sampleNFTs/TestFragmentLiteRefNFT.sol"; import "../src/sampleNFTs/TestBaseNFT.sol"; import "../src/sampleNFTs/TestPatchworkNFT.sol"; +import "../src/sampleNFTs/TestMultiFragmentNFT.sol"; contract PatchworkProtocolTest is Test { event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); @@ -18,6 +19,7 @@ contract PatchworkProtocolTest is Test { TestPatchworkNFT testPatchworkNFT; TestPatchLiteRefNFT testPatchLiteRefNFT; TestFragmentLiteRefNFT testFragmentLiteRefNFT; + TestMultiFragmentNFT testMultiFragmentNFT; string scopeName; address defaultUser; @@ -46,6 +48,8 @@ contract PatchworkProtocolTest is Test { testFragmentLiteRefNFT = new TestFragmentLiteRefNFT(address(prot)); vm.prank(scopeOwner); testPatchworkNFT = new TestPatchworkNFT(address(prot)); + vm.prank(scopeOwner); + testMultiFragmentNFT = new TestMultiFragmentNFT(address(prot)); } function testScopeOwnerOperator() public { @@ -230,7 +234,7 @@ contract PatchworkProtocolTest is Test { assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), userAddress); testFragmentLiteRefNFT.setTestLockOverride(true); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssignedInScope.selector, scopeName, address(testFragmentLiteRefNFT), fragmentTokenId)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(testFragmentLiteRefNFT), fragmentTokenId)); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); testFragmentLiteRefNFT.setTestLockOverride(false); vm.stopPrank(); @@ -245,18 +249,39 @@ contract PatchworkProtocolTest is Test { uint256 fragmentTokenId1 = testFragmentLiteRefNFT.mint(userAddress); uint256 fragmentTokenId2 = testFragmentLiteRefNFT.mint(userAddress); + uint256 multi1 = testMultiFragmentNFT.mint(userAddress); //Register testPatchLiteRefNFT to testPatchLiteRefNFT testFragmentLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + testFragmentLiteRefNFT.registerReferenceAddress(address(testMultiFragmentNFT)); + // Single vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId1, address(testFragmentLiteRefNFT), fragmentTokenId2); + // Multi vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); - prot.unassignNFT(address(testFragmentLiteRefNFT), fragmentTokenId1); + prot.assignNFT(address(testMultiFragmentNFT), multi1, address(testFragmentLiteRefNFT), fragmentTokenId2); address[] memory fragmentAddresses = new address[](1); uint256[] memory fragments = new uint256[](1); fragmentAddresses[0] = address(testFragmentLiteRefNFT); fragments[0] = fragmentTokenId1; vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); prot.batchAssignNFT(fragmentAddresses, fragments, address(testFragmentLiteRefNFT), fragmentTokenId2); + // Claim scope to get assignments done to test unassign + prot.claimScope(scopeName); + prot.setScopeRules(scopeName, false, false, false); + prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId1, address(testFragmentLiteRefNFT), fragmentTokenId2); + prot.assignNFT(address(testMultiFragmentNFT), multi1, address(testFragmentLiteRefNFT), fragmentTokenId2); + testFragmentLiteRefNFT.setScopeName("foo"); + testMultiFragmentNFT.setScopeName("foo"); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, "foo")); + prot.unassignNFT(address(testFragmentLiteRefNFT), fragmentTokenId1, address(testFragmentLiteRefNFT), fragmentTokenId2); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, "foo")); + prot.unassignNFT(address(testMultiFragmentNFT), multi1, address(testFragmentLiteRefNFT), fragmentTokenId2); + } + + function testUnsupportedNFTUnassign() public { + uint256 t1 = testBaseNFT.mint(userAddress); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + prot.unassignNFT(address(testBaseNFT), t1, address(testBaseNFT), t1); } function testScopeTransferCannotBeFrontrun() public { @@ -286,20 +311,24 @@ contract PatchworkProtocolTest is Test { uint256 user2FragmentTokenId = testFragmentLiteRefNFT.mint(user2Address); assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), userAddress); + // Not whitelisted vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, scopeName, address(testFragmentLiteRefNFT))); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); vm.prank(scopeOwner); prot.addWhitelist(scopeName, address(testFragmentLiteRefNFT)); + // user 2 does not own either of these vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); vm.prank(user2Address); prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); + // fragment and target have different owners vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); vm.prank(user2Address); prot.assignNFT(address(testFragmentLiteRefNFT), user2FragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); + // fragment and target have different owners vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.startPrank(userAddress); prot.assignNFT(address(testFragmentLiteRefNFT), user2FragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); @@ -310,13 +339,15 @@ contract PatchworkProtocolTest is Test { assertEq(tokenId, patchTokenId); assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), userAddress); vm.stopPrank(); + // not owned by user 2 vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); vm.prank(user2Address); - prot.unassignNFT(address(testFragmentLiteRefNFT), fragmentTokenId); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragmentTokenId); vm.startPrank(userAddress); - prot.unassignNFT(address(testFragmentLiteRefNFT), fragmentTokenId); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragmentTokenId); + // not currently assigned vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssigned.selector, address(testFragmentLiteRefNFT), fragmentTokenId)); - prot.unassignNFT(address(testFragmentLiteRefNFT), fragmentTokenId); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragmentTokenId); } function testDontAssignSomeoneElsesNFT() public { @@ -336,7 +367,7 @@ contract PatchworkProtocolTest is Test { function testUnassignNFT() public { vm.expectRevert(); // not unassignable - prot.unassignNFT(address(1), 1); + prot.unassignSingleNFT(address(1), 1); uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); uint256 fragment1 = testFragmentLiteRefNFT.mint(userAddress); @@ -359,17 +390,17 @@ contract PatchworkProtocolTest is Test { vm.stopPrank(); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); - prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); vm.startPrank(scopeOwner); - prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); // Now Id1 -> Id, unassign Id1 from Id - prot.unassignNFT(address(testFragmentLiteRefNFT), fragment1); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment1); // Assign Id1 -> Id prot.assignNFT(address(testFragmentLiteRefNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); // Assign Id2 -> Id1 prot.assignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), fragment1); // Now Id2 -> Id1 -> Id, unassign Id1 from Id - prot.unassignNFT(address(testFragmentLiteRefNFT), fragment1); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment1); // Assign Id1 -> Id prot.assignNFT(address(testFragmentLiteRefNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); @@ -381,16 +412,20 @@ contract PatchworkProtocolTest is Test { testFragmentLiteRefNFT.setGetLiteRefOverride(true, 0); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(testFragmentLiteRefNFT))); - prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); testFragmentLiteRefNFT.setGetLiteRefOverride(false, 0); + // Revert b/c this isn't the expected assignment given explicitly + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssignedToTarget.selector, address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), 15000)); + prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), 15000); + // Now Id2 -> Id1 -> Id where Id belongs to 7, unassign Id2 from Id1 and check new ownership - prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); assertEq(testFragmentLiteRefNFT.ownerOf(fragment2), address(7)); testFragmentLiteRefNFT.setGetAssignedToOverride(true, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.RefNotFoundInScope.selector, scopeName, address(testPatchLiteRefNFT), address(testFragmentLiteRefNFT), fragment2)); - prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.RefNotFound.selector, address(testPatchLiteRefNFT), address(testFragmentLiteRefNFT), fragment2)); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); testFragmentLiteRefNFT.setGetAssignedToOverride(false, address(testPatchLiteRefNFT)); vm.stopPrank(); @@ -402,6 +437,25 @@ contract PatchworkProtocolTest is Test { vm.stopPrank(); } + function testUnassignMultiNFT() public { + vm.expectRevert(); // not unassignable + prot.unassignMultiNFT(address(1), 1, address(1), 1); + + uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); + uint256 fragment1 = testMultiFragmentNFT.mint(userAddress); + + vm.startPrank(scopeOwner); + prot.claimScope(scopeName); + prot.setScopeRules(scopeName, false, false, false); + uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + + testPatchLiteRefNFT.registerReferenceAddress(address(testMultiFragmentNFT)); + prot.assignNFT(address(testMultiFragmentNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); + prot.unassignNFT(address(testMultiFragmentNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssignedToTarget.selector, address(testMultiFragmentNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId)); + prot.unassignNFT(address(testMultiFragmentNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); + } + function testBatchAssignNFT() public { uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); @@ -417,11 +471,13 @@ contract PatchworkProtocolTest is Test { fragments[i] = testFragmentLiteRefNFT.mint(userAddress); } + // Fragment must be registered vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(testFragmentLiteRefNFT))); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); uint8 refId = testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); vm.stopPrank(); + // User may not assign without userAssign enabled vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); vm.prank(userAddress); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); @@ -429,17 +485,24 @@ contract PatchworkProtocolTest is Test { vm.prank(scopeOwner); prot.setScopeRules(scopeName, false, false, true); vm.startPrank(scopeOwner); + // Whitelist enabled requires whitelisted (both patch and fragment) + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, scopeName, address(testPatchLiteRefNFT))); + prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); + + prot.addWhitelist(scopeName, address(testPatchLiteRefNFT)); + // Whitelist enabled requires whitelisted (now just fragment) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, scopeName, address(testFragmentLiteRefNFT))); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); prot.addWhitelist(scopeName, address(testFragmentLiteRefNFT)); + // Inputs do not match length vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.BadInputLengths.selector)); prot.batchAssignNFT(new address[](1), fragments, address(testPatchLiteRefNFT), patchTokenId); - vm.stopPrank(); vm.prank(userAddress); testPatchLiteRefNFT.setFrozen(patchTokenId, true); + // It's frozen (patch) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testPatchLiteRefNFT), patchTokenId)); vm.prank(scopeOwner); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); @@ -448,6 +511,7 @@ contract PatchworkProtocolTest is Test { vm.prank(userAddress); testFragmentLiteRefNFT.setFrozen(fragments[0], true); + // It's frozen (fragment) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragments[0])); vm.prank(scopeOwner); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); @@ -456,6 +520,7 @@ contract PatchworkProtocolTest is Test { vm.prank(scopeOwner); testPatchLiteRefNFT.redactReferenceAddress(refId); + // Fragment was redacted vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentRedacted.selector, address(testFragmentLiteRefNFT))); vm.prank(scopeOwner); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); @@ -466,12 +531,14 @@ contract PatchworkProtocolTest is Test { uint256[] memory selfFrag = new uint256[](1); selfAddr[0] = address(testPatchLiteRefNFT); selfFrag[0] = patchTokenId; + // Self-assignment vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.SelfAssignmentNotAllowed.selector, selfAddr[0], selfFrag[0])); vm.prank(scopeOwner); prot.batchAssignNFT(selfAddr, selfFrag, address(testPatchLiteRefNFT), patchTokenId); vm.prank(userAddress); testFragmentLiteRefNFT.setLocked(fragments[0], true); + // Fragment is locked vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Locked.selector, address(testFragmentLiteRefNFT), fragments[0])); vm.prank(scopeOwner); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); @@ -483,6 +550,7 @@ contract PatchworkProtocolTest is Test { uint256[] memory otherUserFrag = new uint256[](1); otherUserAddr[0] = address(testFragmentLiteRefNFT); otherUserFrag[0] = testFragmentLiteRefNFT.mint(user2Address); + // Target and fragment not same owner vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, scopeOwner)); vm.prank(scopeOwner); prot.batchAssignNFT(otherUserAddr, otherUserFrag, address(testPatchLiteRefNFT), patchTokenId); @@ -499,7 +567,7 @@ contract PatchworkProtocolTest is Test { testFragmentLiteRefNFT.setTestLockOverride(true); // setup for next test part } - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssignedInScope.selector, scopeName, fragmentAddresses[0], fragments[0])); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, fragmentAddresses[0], fragments[0])); vm.prank(scopeOwner); prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); } @@ -731,21 +799,22 @@ contract PatchworkProtocolTest is Test { vm.stopPrank(); vm.prank(userAddress); testPatchLiteRefNFT.setFrozen(patchTokenId, true); + // It will return that the fragment is frozen even though the patch is the root cause, because all assigned to the patch inherit the freeze vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragment2)); vm.prank(scopeOwner); // Now Id2 -> Id1 -> Id, unassign Id2 from Id1 - prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); vm.prank(userAddress); testPatchLiteRefNFT.setFrozen(patchTokenId, false); assertEq(2, testPatchLiteRefNFT.getFreezeNonce(patchTokenId)); vm.startPrank(scopeOwner); // Now Id2 -> Id1 -> Id, unassign Id2 from Id1 - prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); vm.stopPrank(); vm.startPrank(scopeOwner); // Unassign Id1 from patch - prot.unassignNFT(address(testFragmentLiteRefNFT), fragment1); + prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment1); vm.stopPrank(); // Lock the fragment, shouldn't allow assignment to anything From 06c3890c8adea16708f382e65c80935254caf8a9 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Wed, 1 Nov 2023 12:58:12 -0700 Subject: [PATCH 12/63] Made scope change events use plain string instead of index (#33) (#35) From 2ced6145ad70ed84bdb6dcfc0f904c38e3769844 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 3 Nov 2023 09:05:46 -0700 Subject: [PATCH 13/63] Allow patches to be fragments (#36) * Initial changes to support weak refs * Fragments can be patches now --- src/IPatchworkAccountPatch.sol | 6 - src/IPatchworkAssignableNFT.sol | 6 - src/IPatchworkPatch.sol | 6 - src/PatchworkAccountPatch.sol | 5 - src/PatchworkFragmentMulti.sol | 5 - src/PatchworkFragmentSingle.sol | 5 - src/PatchworkPatch.sol | 5 - src/sampleNFTs/TestPatchFragmentNFT.sol | 145 ++++++++++++++++++++++++ test/PatchworkAccountPatch.t.sol | 5 +- test/PatchworkFragmentMulti.t.sol | 6 - test/PatchworkFragmentSingle.t.sol | 5 - test/PatchworkPatch.t.sol | 34 +++++- 12 files changed, 177 insertions(+), 56 deletions(-) create mode 100644 src/sampleNFTs/TestPatchFragmentNFT.sol diff --git a/src/IPatchworkAccountPatch.sol b/src/IPatchworkAccountPatch.sol index e837448..13d8916 100644 --- a/src/IPatchworkAccountPatch.sol +++ b/src/IPatchworkAccountPatch.sol @@ -20,10 +20,4 @@ interface IPatchworkAccountPatch { @return tokenId ID of the newly minted token */ function mintPatch(address owner, address originalAccountAddress) external returns (uint256 tokenId); - - /** - @notice A deliberately incompatible function to block implementing both assignable and patch - @return bytes3 Always returns 0x00 - */ - function patchworkCompatible_() external pure returns (bytes3); } \ No newline at end of file diff --git a/src/IPatchworkAssignableNFT.sol b/src/IPatchworkAssignableNFT.sol index af6ddb1..2a1dd48 100644 --- a/src/IPatchworkAssignableNFT.sol +++ b/src/IPatchworkAssignableNFT.sol @@ -32,10 +32,4 @@ interface IPatchworkAssignableNFT { @param scopeName the scope name of the contract to assign to */ function allowAssignment(uint256 ourTokenId, address target, uint256 targetTokenId, address targetOwner, address by, string memory scopeName) external returns (bool); - - /** - @notice A deliberately incompatible function to block implementing both assignable and patch - @return bytes2 Always returns 0x0000 - */ - function patchworkCompatible_() external pure returns (bytes2); } \ No newline at end of file diff --git a/src/IPatchworkPatch.sol b/src/IPatchworkPatch.sol index 0b92ed4..3f9e1d2 100644 --- a/src/IPatchworkPatch.sol +++ b/src/IPatchworkPatch.sol @@ -34,10 +34,4 @@ interface IPatchworkPatch { @return address Address of the owner */ function unpatchedOwnerOf(uint256 tokenId) external returns (address); - - /** - @notice A deliberately incompatible function to block implementing both assignable and patch - @return bytes1 Always returns 0x00 - */ - function patchworkCompatible_() external pure returns (bytes1); } \ No newline at end of file diff --git a/src/PatchworkAccountPatch.sol b/src/PatchworkAccountPatch.sol index 3c39341..cabb097 100644 --- a/src/PatchworkAccountPatch.sol +++ b/src/PatchworkAccountPatch.sol @@ -46,9 +46,4 @@ abstract contract PatchworkAccountPatch is PatchworkNFT, IPatchworkAccountPatch function _burn(uint256 /*tokenId*/) internal virtual override { revert IPatchworkProtocol.UnsupportedOperation(); } - - /** - @dev See {IPatchworkPatch-patchworkCompatible_} - */ - function patchworkCompatible_() external pure returns (bytes3) {} } \ No newline at end of file diff --git a/src/PatchworkFragmentMulti.sol b/src/PatchworkFragmentMulti.sol index 5c9868f..9f60c33 100644 --- a/src/PatchworkFragmentMulti.sol +++ b/src/PatchworkFragmentMulti.sol @@ -133,9 +133,4 @@ abstract contract PatchworkFragmentMulti is PatchworkNFT, IPatchworkMultiAssigna // By default allow multi assignments public return true; } - - /** - @dev See {IPatchworkNFT-patchworkCompatible_} - */ - function patchworkCompatible_() external pure returns (bytes2) {} } \ No newline at end of file diff --git a/src/PatchworkFragmentSingle.sol b/src/PatchworkFragmentSingle.sol index 4b98a4d..ab4f0b5 100644 --- a/src/PatchworkFragmentSingle.sol +++ b/src/PatchworkFragmentSingle.sol @@ -139,9 +139,4 @@ abstract contract PatchworkFragmentSingle is PatchworkNFT, IPatchworkSingleAssig require(_assignments[tokenId].tokenAddr == address(0), "cannot setLocked assigned fragment"); super.setLocked(tokenId, locked_); } - - /** - @dev See {IPatchworkNFT-patchworkCompatible_} - */ - function patchworkCompatible_() external pure returns (bytes2) {} } \ No newline at end of file diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index e61d76f..21cdf1d 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -96,9 +96,4 @@ abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { function _burn(uint256 /*tokenId*/) internal virtual override { revert IPatchworkProtocol.UnsupportedOperation(); } - - /** - @dev See {IPatchworkPatch-patchworkCompatible_} - */ - function patchworkCompatible_() external pure returns (bytes1) {} } \ No newline at end of file diff --git a/src/sampleNFTs/TestPatchFragmentNFT.sol b/src/sampleNFTs/TestPatchFragmentNFT.sol new file mode 100644 index 0000000..e706cf7 --- /dev/null +++ b/src/sampleNFTs/TestPatchFragmentNFT.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +/* + Prototype - Generated Patchwork Meta contract for Totem NFT. + + Is attached to any normal NFT and is scoped for a specific application. + + Has metadata as defined in totem-metadata.json +*/ + +import "../PatchworkPatch.sol"; +import "../PatchworkFragmentSingle.sol"; + +struct TestPatchFragmentNFTMetadata { + uint16 xp; + uint8 level; + uint16 xpLost; + uint16 stakedMade; + uint16 stakedCorrect; + uint8 evolution; + string nickname; +} + +contract TestPatchFragmentNFT is PatchworkPatch, PatchworkFragmentSingle { + + uint256 _nextTokenId; + + constructor(address manager_) PatchworkNFT("testscope", "TestPatchFragment", "TPLR", msg.sender, manager_) PatchworkFragmentSingle() { + } + + // ERC-165 + function supportsInterface(bytes4 interfaceID) public view virtual override(PatchworkPatch, PatchworkFragmentSingle) returns (bool) { + return PatchworkFragmentSingle.supportsInterface(interfaceID) || + PatchworkPatch.supportsInterface(interfaceID); + } + + function schemaURI() pure external override returns (string memory) { + return "https://mything/my-metadata.json"; + } + + function imageURI(uint256 _tokenId) pure external override returns (string memory) {} + + function setManager(address manager_) external { + require(_checkWriteAuth()); + _manager = manager_; + } + + function getScopeName() public view virtual override(PatchworkPatch, PatchworkFragmentSingle) returns (string memory) { + return _scopeName; + } + + function setLocked(uint256 tokenId, bool locked_) public view virtual override(PatchworkPatch, PatchworkFragmentSingle) { + return PatchworkPatch.setLocked(tokenId, locked_); + } + + function locked(uint256 /* tokenId */) public pure virtual override(PatchworkPatch, PatchworkFragmentSingle) returns (bool) { + return false; + } + + function ownerOf(uint256 tokenId) public view virtual override(PatchworkPatch, PatchworkFragmentSingle) returns (address) { + return PatchworkPatch.ownerOf(tokenId); + } + + function updateOwnership(uint256 tokenId) public virtual override(PatchworkPatch, PatchworkFragmentSingle) { + PatchworkPatch.updateOwnership(tokenId); + } + + /* + Hard coded prototype schema is: + slot 0 offset 0 = artifactIDs (spans 2) - also we need special built-in handling for < 256 bit IDs + slot 2 offset 0 = xp + slot 2 offset 16 = level + slot 2 offset 24 = xpLost + slot 2 offset 40 = stakedMade + slot 2 offset 56 = stakedCorrect + slot 2 offset 72 = evolution + slot 2 offset 80 = nickname + */ + function schema() pure external override returns (MetadataSchema memory) { + MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](8); + entries[1] = MetadataSchemaEntry(1, 1, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 0, "xp"); + entries[2] = MetadataSchemaEntry(2, 2, FieldType.UINT8, 0, FieldVisibility.PUBLIC, 2, 16, "level"); + entries[3] = MetadataSchemaEntry(3, 0, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 24, "xpLost"); + entries[4] = MetadataSchemaEntry(4, 0, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 40, "stakedMade"); + entries[5] = MetadataSchemaEntry(5, 0, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 56, "stakedCorrect"); + entries[6] = MetadataSchemaEntry(6, 0, FieldType.UINT8, 0, FieldVisibility.PUBLIC, 2, 72, "evolution"); + entries[7] = MetadataSchemaEntry(7, 0, FieldType.CHAR16, 0, FieldVisibility.PUBLIC, 2, 80, "nickname"); + return MetadataSchema(1, entries); + } + + function packMetadata(TestPatchFragmentNFTMetadata memory data) public pure returns (uint256[] memory slots) { + bytes32 nickname; + bytes memory ns = bytes(data.nickname); + + assembly { + nickname := mload(add(ns, 32)) + } + slots = new uint256[](1); + slots[0] = uint256(data.xp) | uint256(data.level) << 16 | uint256(data.xpLost) << 24 | uint256(data.stakedMade) << 40 | uint256(data.stakedCorrect) << 56 | uint256(data.evolution) << 72 | uint256(nickname) >> 128 << 80; + return slots; + } + + function storeMetadata(uint256 _tokenId, TestPatchFragmentNFTMetadata memory data) public { + require(_checkTokenWriteAuth(_tokenId), "not authorized"); + _metadataStorage[_tokenId] = packMetadata(data); + } + + function unpackMetadata(uint256[] memory slots) public pure returns (TestPatchFragmentNFTMetadata memory data) { + data.xp = uint16(slots[0]); + data.level = uint8(slots[0] >> 16); + data.xpLost = uint16(slots[0] >> 24); + data.stakedMade = uint16(slots[0] >> 40); + data.stakedCorrect = uint16(slots[0] >> 56); + data.evolution = uint8(slots[0] >> 72); + data.nickname = string(abi.encodePacked(bytes16(uint128(slots[0] >> 80)))); + return data; + } + + function loadMetadata(uint256 _tokenId) public view returns (TestPatchFragmentNFTMetadata memory data) { + return unpackMetadata(_metadataStorage[_tokenId]); + } + + function mintPatch(address originalNFTOwner, address originalNFTAddress, uint originalNFTTokenId) external returns (uint256 tokenId){ + if (msg.sender != _manager) { + revert(); + } + // Just for testing + tokenId = _nextTokenId; + _nextTokenId++; + _storePatch(tokenId, originalNFTAddress, originalNFTTokenId); + _safeMint(originalNFTOwner, tokenId); + _metadataStorage[tokenId] = new uint256[](3); + return tokenId; + } + + function burn(uint256 tokenId) public { + // test only + _burn(tokenId); + } + + function _burn(uint256 tokenId) internal virtual override(PatchworkPatch, ERC721) { + return PatchworkPatch._burn(tokenId); + } +} \ No newline at end of file diff --git a/test/PatchworkAccountPatch.t.sol b/test/PatchworkAccountPatch.t.sol index eac5abd..ccbc1fa 100644 --- a/test/PatchworkAccountPatch.t.sol +++ b/test/PatchworkAccountPatch.t.sol @@ -99,8 +99,5 @@ contract PatchworkAccountPatchTest is Test { _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); } - function testPatchworkCompatible() public { - TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); - testAccountPatchNFT.patchworkCompatible_(); - } + } \ No newline at end of file diff --git a/test/PatchworkFragmentMulti.t.sol b/test/PatchworkFragmentMulti.t.sol index 370254b..a02d7b7 100644 --- a/test/PatchworkFragmentMulti.t.sol +++ b/test/PatchworkFragmentMulti.t.sol @@ -193,10 +193,4 @@ contract PatchworkFragmentMultiTest is Test { uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress); _prot.assignNFT(address(multi), m1, address(_testFragmentLiteRefNFT), lr1); } - - function testPatchworkCompatible() public { - TestMultiFragmentNFT multi = new TestMultiFragmentNFT(address(_prot)); - multi.patchworkCompatible_(); - } - } \ No newline at end of file diff --git a/test/PatchworkFragmentSingle.t.sol b/test/PatchworkFragmentSingle.t.sol index 7905e32..b42f81e 100644 --- a/test/PatchworkFragmentSingle.t.sol +++ b/test/PatchworkFragmentSingle.t.sol @@ -58,11 +58,6 @@ contract PatchworkFragmentSingleTest is Test { _testFragmentLiteRefNFT.onAssignedTransfer(address(0), address(1), 1); } - function testPatchworkCompatible() public { - bytes2 r2 = _testFragmentLiteRefNFT.patchworkCompatible_(); - assertEq(0, r2); - } - function testLiteref56bitlimit() public { vm.prank(_scopeOwner); uint8 r1 = _testFragmentLiteRefNFT.registerReferenceAddress(address(1)); diff --git a/test/PatchworkPatch.t.sol b/test/PatchworkPatch.t.sol index 000c29f..1bc7f1c 100644 --- a/test/PatchworkPatch.t.sol +++ b/test/PatchworkPatch.t.sol @@ -7,6 +7,7 @@ import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; import "../src/sampleNFTs/TestPatchLiteRefNFT.sol"; import "../src/sampleNFTs/TestBaseNFT.sol"; +import "../src/sampleNFTs/TestPatchFragmentNFT.sol"; contract PatchworkPatchTest is Test { PatchworkProtocol _prot; @@ -73,8 +74,35 @@ contract PatchworkPatchTest is Test { _testPatchLiteRefNFT.burn(patchTokenId); } - function testPatchworkCompatible() public { - bytes1 r1 = _testPatchLiteRefNFT.patchworkCompatible_(); - assertEq(0, r1); + function testPatchFragment() public { + vm.startPrank(_scopeOwner); + uint256 baseTokenId = _testBaseNFT.mint(_userAddress); + uint256 baseTokenId2 = _testBaseNFT.mint(_user2Address); + uint256 baseTokenId3 = _testBaseNFT.mint(_userAddress); + TestPatchFragmentNFT testPatchFragmentNFT = new TestPatchFragmentNFT(address(_prot)); + _testPatchLiteRefNFT.registerReferenceAddress(address(testPatchFragmentNFT)); + uint256 liteRefId = _prot.createPatch(address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + uint256 liteRefId2 = _prot.createPatch(address(_testBaseNFT), baseTokenId2, address(_testPatchLiteRefNFT)); + uint256 fragmentTokenId = _prot.createPatch(address(_testBaseNFT), baseTokenId3, address(testPatchFragmentNFT)); + // cannot assign patch to a literef that this person does not own + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); + _prot.assignNFT(address(testPatchFragmentNFT), fragmentTokenId, address(_testPatchLiteRefNFT), liteRefId2); + // can assign to same owner + _prot.assignNFT(address(testPatchFragmentNFT), fragmentTokenId, address(_testPatchLiteRefNFT), liteRefId); + // transfer the underlying patched nft and check ownership + vm.stopPrank(); + assertEq(_userAddress, _testBaseNFT.ownerOf(baseTokenId)); + assertEq(_userAddress, _testPatchLiteRefNFT.ownerOf(baseTokenId)); + assertEq(_userAddress, testPatchFragmentNFT.ownerOf(fragmentTokenId)); + vm.prank(_userAddress); + _testBaseNFT.transferFrom(_userAddress, _user2Address, baseTokenId); + assertEq(_user2Address, _testBaseNFT.ownerOf(baseTokenId)); + assertEq(_user2Address, _testPatchLiteRefNFT.ownerOf(baseTokenId)); + assertEq(_userAddress, testPatchFragmentNFT.ownerOf(fragmentTokenId)); + vm.prank(_userAddress); + _testBaseNFT.transferFrom(_userAddress, _user2Address, baseTokenId3); + assertEq(_user2Address, testPatchFragmentNFT.ownerOf(fragmentTokenId)); + _prot.updateOwnershipTree(address(testPatchFragmentNFT), fragmentTokenId); + assertEq(_user2Address, testPatchFragmentNFT.ownerOf(fragmentTokenId)); } } \ No newline at end of file From e25f830fbff92e7920279efc5cab2c40ca5ba880 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 3 Nov 2023 11:07:29 -0700 Subject: [PATCH 14/63] Made scope change events use plain string instead of index (#33) (#38) From 9f2ee67f06df931f3504f34d9b4cad42b3eaaf1a Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 3 Nov 2023 11:19:37 -0700 Subject: [PATCH 15/63] Moved test NFTs and fixed linter with proto test (#39) --- test/PatchworkAccountPatch.t.sol | 2 +- test/PatchworkFragmentMulti.t.sol | 6 +- test/PatchworkFragmentSingle.t.sol | 4 +- test/PatchworkNFT.t.sol | 2 +- test/PatchworkNFTReferences.t.sol | 6 +- test/PatchworkPatch.t.sol | 6 +- test/PatchworkProtocol.t.sol | 1144 ++++++++--------- test/TestPatchLiteRefNFT.t.sol | 2 +- .../nfts}/TestAccountPatchNFT.sol | 2 +- {src/sampleNFTs => test/nfts}/TestBaseNFT.sol | 0 .../nfts}/TestFragmentLiteRefNFT.sol | 4 +- .../nfts}/TestMultiFragmentNFT.sol | 4 +- .../nfts}/TestPatchFragmentNFT.sol | 4 +- .../nfts}/TestPatchLiteRefNFT.sol | 4 +- .../nfts}/TestPatchworkNFT.sol | 2 +- 15 files changed, 596 insertions(+), 596 deletions(-) rename {src/sampleNFTs => test/nfts}/TestAccountPatchNFT.sol (97%) rename {src/sampleNFTs => test/nfts}/TestBaseNFT.sol (100%) rename {src/sampleNFTs => test/nfts}/TestFragmentLiteRefNFT.sol (99%) rename {src/sampleNFTs => test/nfts}/TestMultiFragmentNFT.sol (96%) rename {src/sampleNFTs => test/nfts}/TestPatchFragmentNFT.sol (98%) rename {src/sampleNFTs => test/nfts}/TestPatchLiteRefNFT.sol (99%) rename {src/sampleNFTs => test/nfts}/TestPatchworkNFT.sol (96%) diff --git a/test/PatchworkAccountPatch.t.sol b/test/PatchworkAccountPatch.t.sol index ccbc1fa..aa2576a 100644 --- a/test/PatchworkAccountPatch.t.sol +++ b/test/PatchworkAccountPatch.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; -import "../src/sampleNFTs/TestAccountPatchNFT.sol"; +import "./nfts/TestAccountPatchNFT.sol"; contract PatchworkAccountPatchTest is Test { diff --git a/test/PatchworkFragmentMulti.t.sol b/test/PatchworkFragmentMulti.t.sol index a02d7b7..a00b994 100644 --- a/test/PatchworkFragmentMulti.t.sol +++ b/test/PatchworkFragmentMulti.t.sol @@ -5,9 +5,9 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; -import "../src/sampleNFTs/TestFragmentLiteRefNFT.sol"; -import "../src/sampleNFTs/TestBaseNFT.sol"; -import "../src/sampleNFTs/TestMultiFragmentNFT.sol"; +import "./nfts/TestFragmentLiteRefNFT.sol"; +import "./nfts/TestBaseNFT.sol"; +import "./nfts/TestMultiFragmentNFT.sol"; contract PatchworkFragmentMultiTest is Test { PatchworkProtocol _prot; diff --git a/test/PatchworkFragmentSingle.t.sol b/test/PatchworkFragmentSingle.t.sol index b42f81e..ef79893 100644 --- a/test/PatchworkFragmentSingle.t.sol +++ b/test/PatchworkFragmentSingle.t.sol @@ -5,8 +5,8 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; -import "../src/sampleNFTs/TestFragmentLiteRefNFT.sol"; -import "../src/sampleNFTs/TestBaseNFT.sol"; +import "./nfts/TestFragmentLiteRefNFT.sol"; +import "./nfts/TestBaseNFT.sol"; contract PatchworkFragmentSingleTest is Test { PatchworkProtocol _prot; diff --git a/test/PatchworkNFT.t.sol b/test/PatchworkNFT.t.sol index 064e03f..98d85a0 100644 --- a/test/PatchworkNFT.t.sol +++ b/test/PatchworkNFT.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; -import "../src/sampleNFTs/TestPatchworkNFT.sol"; +import "./nfts/TestPatchworkNFT.sol"; contract PatchworkNFTTest is Test { PatchworkProtocol _prot; diff --git a/test/PatchworkNFTReferences.t.sol b/test/PatchworkNFTReferences.t.sol index 7d57425..229d9f2 100644 --- a/test/PatchworkNFTReferences.t.sol +++ b/test/PatchworkNFTReferences.t.sol @@ -5,9 +5,9 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; -import "../src/sampleNFTs/TestPatchLiteRefNFT.sol"; -import "../src/sampleNFTs/TestFragmentLiteRefNFT.sol"; -import "../src/sampleNFTs/TestBaseNFT.sol"; +import "./nfts/TestPatchLiteRefNFT.sol"; +import "./nfts/TestFragmentLiteRefNFT.sol"; +import "./nfts/TestBaseNFT.sol"; contract PatchworkNFTCombinedTest is Test { PatchworkProtocol _prot; diff --git a/test/PatchworkPatch.t.sol b/test/PatchworkPatch.t.sol index 1bc7f1c..a297b6d 100644 --- a/test/PatchworkPatch.t.sol +++ b/test/PatchworkPatch.t.sol @@ -5,9 +5,9 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; -import "../src/sampleNFTs/TestPatchLiteRefNFT.sol"; -import "../src/sampleNFTs/TestBaseNFT.sol"; -import "../src/sampleNFTs/TestPatchFragmentNFT.sol"; +import "./nfts/TestPatchLiteRefNFT.sol"; +import "./nfts/TestBaseNFT.sol"; +import "./nfts/TestPatchFragmentNFT.sol"; contract PatchworkPatchTest is Test { PatchworkProtocol _prot; diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index a09970f..44948e4 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -5,896 +5,896 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; -import "../src/sampleNFTs/TestPatchLiteRefNFT.sol"; -import "../src/sampleNFTs/TestFragmentLiteRefNFT.sol"; -import "../src/sampleNFTs/TestBaseNFT.sol"; -import "../src/sampleNFTs/TestPatchworkNFT.sol"; -import "../src/sampleNFTs/TestMultiFragmentNFT.sol"; +import "./nfts/TestPatchLiteRefNFT.sol"; +import "./nfts/TestFragmentLiteRefNFT.sol"; +import "./nfts/TestBaseNFT.sol"; +import "./nfts/TestPatchworkNFT.sol"; +import "./nfts/TestMultiFragmentNFT.sol"; contract PatchworkProtocolTest is Test { event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - PatchworkProtocol prot; - TestBaseNFT testBaseNFT; - TestPatchworkNFT testPatchworkNFT; - TestPatchLiteRefNFT testPatchLiteRefNFT; - TestFragmentLiteRefNFT testFragmentLiteRefNFT; - TestMultiFragmentNFT testMultiFragmentNFT; + PatchworkProtocol _prot; + TestBaseNFT _testBaseNFT; + TestPatchworkNFT _testPatchworkNFT; + TestPatchLiteRefNFT _testPatchLiteRefNFT; + TestFragmentLiteRefNFT _testFragmentLiteRefNFT; + TestMultiFragmentNFT _testMultiFragmentNFT; - string scopeName; - address defaultUser; - address patchworkOwner; - address userAddress; - address user2Address; - address scopeOwner; + string _scopeName; + address _defaultUser; + address _patchworkOwner; + address _userAddress; + address _user2Address; + address _scopeOwner; function setUp() public { - defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; - patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; - userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; - user2Address = address(550001); - scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; - - vm.prank(patchworkOwner); - prot = new PatchworkProtocol(); - scopeName = "testscope"; - - vm.prank(userAddress); - testBaseNFT = new TestBaseNFT(); - - vm.prank(scopeOwner); - testPatchLiteRefNFT = new TestPatchLiteRefNFT(address(prot)); - vm.prank(scopeOwner); - testFragmentLiteRefNFT = new TestFragmentLiteRefNFT(address(prot)); - vm.prank(scopeOwner); - testPatchworkNFT = new TestPatchworkNFT(address(prot)); - vm.prank(scopeOwner); - testMultiFragmentNFT = new TestMultiFragmentNFT(address(prot)); + _defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; + _patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; + _userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; + _user2Address = address(550001); + _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; + + vm.prank(_patchworkOwner); + _prot = new PatchworkProtocol(); + _scopeName = "testscope"; + + vm.prank(_userAddress); + _testBaseNFT = new TestBaseNFT(); + + vm.prank(_scopeOwner); + _testPatchLiteRefNFT = new TestPatchLiteRefNFT(address(_prot)); + vm.prank(_scopeOwner); + _testFragmentLiteRefNFT = new TestFragmentLiteRefNFT(address(_prot)); + vm.prank(_scopeOwner); + _testPatchworkNFT = new TestPatchworkNFT(address(_prot)); + vm.prank(_scopeOwner); + _testMultiFragmentNFT = new TestMultiFragmentNFT(address(_prot)); } function testScopeOwnerOperator() public { - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - assertEq(prot.getScopeOwner(scopeName), scopeOwner); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeExists.selector, scopeName)); - prot.claimScope(scopeName); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + assertEq(_prot.getScopeOwner(_scopeName), _scopeOwner); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeExists.selector, _scopeName)); + _prot.claimScope(_scopeName); vm.stopPrank(); // Current user is not scope owner so can't transfer it - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, defaultUser)); - prot.transferScopeOwnership(scopeName, address(2)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _prot.transferScopeOwnership(_scopeName, address(2)); // Real owner can transfer it - vm.prank(scopeOwner); - prot.transferScopeOwnership(scopeName, address(3)); - // scopeOwner still owns it until it's accepted - assertEq(prot.getScopeOwner(scopeName), scopeOwner); - assertEq(prot.getScopeOwnerElect(scopeName), address(3)); + vm.prank(_scopeOwner); + _prot.transferScopeOwnership(_scopeName, address(3)); + // _scopeOwner still owns it until it's accepted + assertEq(_prot.getScopeOwner(_scopeName), _scopeOwner); + assertEq(_prot.getScopeOwnerElect(_scopeName), address(3)); // test changing the pending transfer elect - vm.prank(scopeOwner); - prot.transferScopeOwnership(scopeName, address(2)); - assertEq(prot.getScopeOwnerElect(scopeName), address(2)); + vm.prank(_scopeOwner); + _prot.transferScopeOwnership(_scopeName, address(2)); + assertEq(_prot.getScopeOwnerElect(_scopeName), address(2)); // Non-owner may not cancel the transfer vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, address(10))); vm.prank(address(10)); - prot.cancelScopeTransfer(scopeName); + _prot.cancelScopeTransfer(_scopeName); // Real owner can cancel the transfer - vm.prank(scopeOwner); - prot.cancelScopeTransfer(scopeName); - assertEq(prot.getScopeOwnerElect(scopeName), address(0)); + vm.prank(_scopeOwner); + _prot.cancelScopeTransfer(_scopeName); + assertEq(_prot.getScopeOwnerElect(_scopeName), address(0)); // Now retry the transfer - vm.prank(scopeOwner); - prot.transferScopeOwnership(scopeName, address(2)); + vm.prank(_scopeOwner); + _prot.transferScopeOwnership(_scopeName, address(2)); // User 10 is not elect and may not accept the transfer vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, address(10))); vm.prank(address(10)); - prot.acceptScopeTransfer(scopeName); + _prot.acceptScopeTransfer(_scopeName); // Finally real elect accepts scope transfer vm.prank(address(2)); - prot.acceptScopeTransfer(scopeName); - assertEq(prot.getScopeOwner(scopeName), address(2)); - assertEq(prot.getScopeOwnerElect(scopeName), address(0)); + _prot.acceptScopeTransfer(_scopeName); + assertEq(_prot.getScopeOwner(_scopeName), address(2)); + assertEq(_prot.getScopeOwnerElect(_scopeName), address(0)); // Old owner may not transfer it - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, scopeOwner)); - vm.prank(scopeOwner); - prot.transferScopeOwnership(scopeName, address(2)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); + vm.prank(_scopeOwner); + _prot.transferScopeOwnership(_scopeName, address(2)); // New owner may transfer it back to old owner vm.prank(address(2)); - prot.transferScopeOwnership(scopeName, scopeOwner); - vm.prank(scopeOwner); - prot.acceptScopeTransfer(scopeName); - assertEq(prot.getScopeOwner(scopeName), scopeOwner); + _prot.transferScopeOwnership(_scopeName, _scopeOwner); + vm.prank(_scopeOwner); + _prot.acceptScopeTransfer(_scopeName); + assertEq(_prot.getScopeOwner(_scopeName), _scopeOwner); // Non-owner may not add operator vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, address(2))); vm.prank(address(2)); - prot.addOperator(scopeName, address(2)); + _prot.addOperator(_scopeName, address(2)); // Real owner may add operator - vm.prank(scopeOwner); - prot.addOperator(scopeName, address(2)); + vm.prank(_scopeOwner); + _prot.addOperator(_scopeName, address(2)); // Non-owner may not remove operator vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, address(2))); vm.prank(address(2)); - prot.removeOperator(scopeName, address(2)); + _prot.removeOperator(_scopeName, address(2)); // Real owner may remove operator - vm.prank(scopeOwner); - prot.removeOperator(scopeName, address(2)); + vm.prank(_scopeOwner); + _prot.removeOperator(_scopeName, address(2)); // Non-owner may not set scope rules vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, address(2))); vm.prank(address(2)); - prot.setScopeRules(scopeName, true, true, true); + _prot.setScopeRules(_scopeName, true, true, true); } function testCreatePatchNFTNoVerification() public { - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, false); - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - uint256 tokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + uint256 tokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); assertEq(tokenId, 0); } function testCreatePatchNFTUnverified() public { - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, scopeName, address(testPatchLiteRefNFT))); - prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(_testPatchLiteRefNFT))); + _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); } function testCreatePatchNFTVerified() public { - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - vm.prank(userAddress); - prot.addWhitelist(scopeName, address(testPatchLiteRefNFT)); - vm.startPrank(scopeOwner); - prot.addWhitelist(scopeName, address(testPatchLiteRefNFT)); - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - uint256 tokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); + _prot.addWhitelist(_scopeName, address(_testPatchLiteRefNFT)); + vm.startPrank(_scopeOwner); + _prot.addWhitelist(_scopeName, address(_testPatchLiteRefNFT)); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + uint256 tokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); assertEq(tokenId, 0); vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - vm.prank(userAddress); - prot.removeWhitelist(scopeName, address(testPatchLiteRefNFT)); - vm.startPrank(scopeOwner); - prot.removeWhitelist(scopeName, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, scopeName, address(testPatchLiteRefNFT))); - tokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId + 1, address(testPatchLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); + _prot.removeWhitelist(_scopeName, address(_testPatchLiteRefNFT)); + vm.startPrank(_scopeOwner); + _prot.removeWhitelist(_scopeName, address(_testPatchLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(_testPatchLiteRefNFT))); + tokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId + 1, address(_testPatchLiteRefNFT)); } function testUserPermissions() public { - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - prot.addWhitelist(scopeName, address(testPatchLiteRefNFT)); - prot.addWhitelist(scopeName, address(testFragmentLiteRefNFT)); - uint256 fragmentTokenId = testFragmentLiteRefNFT.mint(userAddress); - assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), userAddress); - //Register artifactNFT to testPatchLiteRefNFT - testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + _prot.addWhitelist(_scopeName, address(_testPatchLiteRefNFT)); + _prot.addWhitelist(_scopeName, address(_testFragmentLiteRefNFT)); + uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress); + assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _userAddress); + //Register artifactNFT to _testPatchLiteRefNFT + _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); vm.stopPrank(); - vm.startPrank(userAddress); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + vm.startPrank(_userAddress); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); vm.stopPrank(); - vm.prank(scopeOwner); - prot.setScopeRules(scopeName, true, false, true); - vm.startPrank(userAddress); - patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); + vm.prank(_scopeOwner); + _prot.setScopeRules(_scopeName, true, false, true); + vm.startPrank(_userAddress); + patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); - vm.prank(scopeOwner); - prot.setScopeRules(scopeName, true, true, true); - vm.prank(userAddress); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); + vm.prank(_scopeOwner); + _prot.setScopeRules(_scopeName, true, true, true); + vm.prank(_userAddress); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); // expect revert - vm.prank(userAddress); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TransferBlockedByAssignment.selector, testFragmentLiteRefNFT, fragmentTokenId)); - testFragmentLiteRefNFT.transferFrom(userAddress, address(5), fragmentTokenId); + vm.prank(_userAddress); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TransferBlockedByAssignment.selector, _testFragmentLiteRefNFT, fragmentTokenId)); + _testFragmentLiteRefNFT.transferFrom(_userAddress, address(5), fragmentTokenId); } function testAssignNFT() public { vm.expectRevert(); // not assignable - prot.assignNFT(address(1), 1, address(1), 1); - - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); - prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); - - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, false); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.AlreadyPatched.selector, address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT))); - patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + _prot.assignNFT(address(1), 1, address(1), 1); + + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, _scopeName)); + _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.AlreadyPatched.selector, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT))); + patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); - uint256 fragmentTokenId = testFragmentLiteRefNFT.mint(userAddress); - assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), userAddress); + uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress); + assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _userAddress); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(testFragmentLiteRefNFT))); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testFragmentLiteRefNFT))); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); - //Register artifactNFT to testPatchLiteRefNFT - testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + //Register artifactNFT to _testPatchLiteRefNFT + _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.SelfAssignmentNotAllowed.selector, address(testFragmentLiteRefNFT), fragmentTokenId)); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testFragmentLiteRefNFT), fragmentTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.SelfAssignmentNotAllowed.selector, address(_testFragmentLiteRefNFT), fragmentTokenId)); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testFragmentLiteRefNFT), fragmentTokenId); vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - vm.prank(userAddress); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); // cover called from non-owner/op with no allowUserAssign - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); - vm.startPrank(scopeOwner); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); - (address addr, uint256 tokenId) = testFragmentLiteRefNFT.getAssignedTo(fragmentTokenId); - assertEq(addr, address(testPatchLiteRefNFT)); + vm.startPrank(_scopeOwner); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + (address addr, uint256 tokenId) = _testFragmentLiteRefNFT.getAssignedTo(fragmentTokenId); + assertEq(addr, address(_testPatchLiteRefNFT)); assertEq(tokenId, patchTokenId); - assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), userAddress); + assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _userAddress); - testFragmentLiteRefNFT.setTestLockOverride(true); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(testFragmentLiteRefNFT), fragmentTokenId)); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); - testFragmentLiteRefNFT.setTestLockOverride(false); + _testFragmentLiteRefNFT.setTestLockOverride(true); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(_testFragmentLiteRefNFT), fragmentTokenId)); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _testFragmentLiteRefNFT.setTestLockOverride(false); vm.stopPrank(); - vm.prank(userAddress); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TransferNotAllowed.selector, address(testPatchLiteRefNFT), patchTokenId)); - testPatchLiteRefNFT.transferFrom(userAddress, user2Address, patchTokenId); + vm.prank(_userAddress); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TransferNotAllowed.selector, address(_testPatchLiteRefNFT), patchTokenId)); + _testPatchLiteRefNFT.transferFrom(_userAddress, _user2Address, patchTokenId); } function testScopeDoesNotExist() public { - vm.startPrank(scopeOwner); + vm.startPrank(_scopeOwner); - uint256 fragmentTokenId1 = testFragmentLiteRefNFT.mint(userAddress); - uint256 fragmentTokenId2 = testFragmentLiteRefNFT.mint(userAddress); - uint256 multi1 = testMultiFragmentNFT.mint(userAddress); - //Register testPatchLiteRefNFT to testPatchLiteRefNFT - testFragmentLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - testFragmentLiteRefNFT.registerReferenceAddress(address(testMultiFragmentNFT)); + uint256 fragmentTokenId1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragmentTokenId2 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 multi1 = _testMultiFragmentNFT.mint(_userAddress); + //Register _testPatchLiteRefNFT to _testPatchLiteRefNFT + _testFragmentLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); + _testFragmentLiteRefNFT.registerReferenceAddress(address(_testMultiFragmentNFT)); // Single - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId1, address(testFragmentLiteRefNFT), fragmentTokenId2); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, _scopeName)); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId1, address(_testFragmentLiteRefNFT), fragmentTokenId2); // Multi - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); - prot.assignNFT(address(testMultiFragmentNFT), multi1, address(testFragmentLiteRefNFT), fragmentTokenId2); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, _scopeName)); + _prot.assignNFT(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2); address[] memory fragmentAddresses = new address[](1); uint256[] memory fragments = new uint256[](1); - fragmentAddresses[0] = address(testFragmentLiteRefNFT); + fragmentAddresses[0] = address(_testFragmentLiteRefNFT); fragments[0] = fragmentTokenId1; - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, scopeName)); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testFragmentLiteRefNFT), fragmentTokenId2); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, _scopeName)); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testFragmentLiteRefNFT), fragmentTokenId2); // Claim scope to get assignments done to test unassign - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, false); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId1, address(testFragmentLiteRefNFT), fragmentTokenId2); - prot.assignNFT(address(testMultiFragmentNFT), multi1, address(testFragmentLiteRefNFT), fragmentTokenId2); - testFragmentLiteRefNFT.setScopeName("foo"); - testMultiFragmentNFT.setScopeName("foo"); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId1, address(_testFragmentLiteRefNFT), fragmentTokenId2); + _prot.assignNFT(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2); + _testFragmentLiteRefNFT.setScopeName("foo"); + _testMultiFragmentNFT.setScopeName("foo"); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, "foo")); - prot.unassignNFT(address(testFragmentLiteRefNFT), fragmentTokenId1, address(testFragmentLiteRefNFT), fragmentTokenId2); + _prot.unassignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId1, address(_testFragmentLiteRefNFT), fragmentTokenId2); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, "foo")); - prot.unassignNFT(address(testMultiFragmentNFT), multi1, address(testFragmentLiteRefNFT), fragmentTokenId2); + _prot.unassignNFT(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2); } function testUnsupportedNFTUnassign() public { - uint256 t1 = testBaseNFT.mint(userAddress); + uint256 t1 = _testBaseNFT.mint(_userAddress); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); - prot.unassignNFT(address(testBaseNFT), t1, address(testBaseNFT), t1); + _prot.unassignNFT(address(_testBaseNFT), t1, address(_testBaseNFT), t1); } function testScopeTransferCannotBeFrontrun() public { address maliciousActor = address(120938); // A malicious actor attempts to preconfigure and transfer a scope to 0 so an unsuspecting actor claims it but it already has operators preconfigured vm.startPrank(maliciousActor); - prot.claimScope("foo"); - prot.addOperator("foo", address(4)); + _prot.claimScope("foo"); + _prot.addOperator("foo", address(4)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeTransferNotAllowed.selector, address(0))); - prot.transferScopeOwnership("foo", address(0)); + _prot.transferScopeOwnership("foo", address(0)); } function testUserAssignNFT() public { - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, true, true, true); - prot.addWhitelist(scopeName, address(testPatchLiteRefNFT)); - //Register artifactNFT to testPatchLiteRefNFT - testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, true, true, true); + _prot.addWhitelist(_scopeName, address(_testPatchLiteRefNFT)); + //Register artifactNFT to _testPatchLiteRefNFT + _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); vm.stopPrank(); - vm.startPrank(userAddress); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + vm.startPrank(_userAddress); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); - uint256 fragmentTokenId = testFragmentLiteRefNFT.mint(userAddress); - uint256 user2FragmentTokenId = testFragmentLiteRefNFT.mint(user2Address); - assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), userAddress); + uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 user2FragmentTokenId = _testFragmentLiteRefNFT.mint(_user2Address); + assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _userAddress); // Not whitelisted - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, scopeName, address(testFragmentLiteRefNFT))); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(_testFragmentLiteRefNFT))); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); - vm.prank(scopeOwner); - prot.addWhitelist(scopeName, address(testFragmentLiteRefNFT)); + vm.prank(_scopeOwner); + _prot.addWhitelist(_scopeName, address(_testFragmentLiteRefNFT)); // user 2 does not own either of these - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); - vm.prank(user2Address); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + vm.prank(_user2Address); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); // fragment and target have different owners - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); - vm.prank(user2Address); - prot.assignNFT(address(testFragmentLiteRefNFT), user2FragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + vm.prank(_user2Address); + _prot.assignNFT(address(_testFragmentLiteRefNFT), user2FragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); // fragment and target have different owners - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - vm.startPrank(userAddress); - prot.assignNFT(address(testFragmentLiteRefNFT), user2FragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.startPrank(_userAddress); + _prot.assignNFT(address(_testFragmentLiteRefNFT), user2FragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); - (address addr, uint256 tokenId) = testFragmentLiteRefNFT.getAssignedTo(fragmentTokenId); - assertEq(addr, address(testPatchLiteRefNFT)); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + (address addr, uint256 tokenId) = _testFragmentLiteRefNFT.getAssignedTo(fragmentTokenId); + assertEq(addr, address(_testPatchLiteRefNFT)); assertEq(tokenId, patchTokenId); - assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), userAddress); + assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _userAddress); vm.stopPrank(); // not owned by user 2 - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); - vm.prank(user2Address); - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragmentTokenId); - vm.startPrank(userAddress); - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragmentTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + vm.prank(_user2Address); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragmentTokenId); + vm.startPrank(_userAddress); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragmentTokenId); // not currently assigned - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssigned.selector, address(testFragmentLiteRefNFT), fragmentTokenId)); - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragmentTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssigned.selector, address(_testFragmentLiteRefNFT), fragmentTokenId)); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragmentTokenId); } function testDontAssignSomeoneElsesNFT() public { - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, false); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); - uint256 fragmentTokenId = testFragmentLiteRefNFT.mint(user2Address); - assertEq(testFragmentLiteRefNFT.ownerOf(fragmentTokenId), user2Address); - //Register artifactNFT to testPatchLiteRefNFT - testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, scopeOwner)); - prot.assignNFT(address(testFragmentLiteRefNFT), fragmentTokenId, address(testPatchLiteRefNFT), patchTokenId); + uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_user2Address); + assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _user2Address); + //Register artifactNFT to _testPatchLiteRefNFT + _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); } function testUnassignNFT() public { vm.expectRevert(); // not unassignable - prot.unassignSingleNFT(address(1), 1); + _prot.unassignSingleNFT(address(1), 1); - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - uint256 fragment1 = testFragmentLiteRefNFT.mint(userAddress); - uint256 fragment2 = testFragmentLiteRefNFT.mint(userAddress); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress); - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, false); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); - testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - //Register testFragmentLiteRefNFT to testFragmentLiteRefNFT to allow recursion - testFragmentLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); + //Register _testFragmentLiteRefNFT to _testFragmentLiteRefNFT to allow recursion + _testFragmentLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); // Assign Id1 -> Id - prot.assignNFT(address(testFragmentLiteRefNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); // Assign Id2 -> Id1 - prot.assignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), fragment1); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); // Now Id2 -> Id1 -> Id, unassign Id2 from Id1 vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - vm.prank(userAddress); - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); - vm.startPrank(scopeOwner); - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); + vm.startPrank(_scopeOwner); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); // Now Id1 -> Id, unassign Id1 from Id - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment1); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment1); // Assign Id1 -> Id - prot.assignNFT(address(testFragmentLiteRefNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); // Assign Id2 -> Id1 - prot.assignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), fragment1); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); // Now Id2 -> Id1 -> Id, unassign Id1 from Id - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment1); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment1); // Assign Id1 -> Id - prot.assignNFT(address(testFragmentLiteRefNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); - vm.startPrank(testBaseNFT.ownerOf(testBaseNFTTokenId)); - // transfer ownership of underlying asset (testBaseNFT) - testBaseNFT.transferFrom(testBaseNFT.ownerOf(testBaseNFTTokenId), address(7), testBaseNFTTokenId); + vm.startPrank(_testBaseNFT.ownerOf(_testBaseNFTTokenId)); + // transfer ownership of underlying asset (_testBaseNFT) + _testBaseNFT.transferFrom(_testBaseNFT.ownerOf(_testBaseNFTTokenId), address(7), _testBaseNFTTokenId); vm.stopPrank(); - vm.startPrank(scopeOwner); + vm.startPrank(_scopeOwner); - testFragmentLiteRefNFT.setGetLiteRefOverride(true, 0); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(testFragmentLiteRefNFT))); - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); - testFragmentLiteRefNFT.setGetLiteRefOverride(false, 0); + _testFragmentLiteRefNFT.setGetLiteRefOverride(true, 0); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testFragmentLiteRefNFT))); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); + _testFragmentLiteRefNFT.setGetLiteRefOverride(false, 0); // Revert b/c this isn't the expected assignment given explicitly - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssignedToTarget.selector, address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), 15000)); - prot.unassignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), 15000); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssignedToTarget.selector, address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), 15000)); + _prot.unassignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), 15000); // Now Id2 -> Id1 -> Id where Id belongs to 7, unassign Id2 from Id1 and check new ownership - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); - assertEq(testFragmentLiteRefNFT.ownerOf(fragment2), address(7)); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); + assertEq(_testFragmentLiteRefNFT.ownerOf(fragment2), address(7)); - testFragmentLiteRefNFT.setGetAssignedToOverride(true, address(testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.RefNotFound.selector, address(testPatchLiteRefNFT), address(testFragmentLiteRefNFT), fragment2)); - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); - testFragmentLiteRefNFT.setGetAssignedToOverride(false, address(testPatchLiteRefNFT)); + _testFragmentLiteRefNFT.setGetAssignedToOverride(true, address(_testPatchLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.RefNotFound.selector, address(_testPatchLiteRefNFT), address(_testFragmentLiteRefNFT), fragment2)); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); + _testFragmentLiteRefNFT.setGetAssignedToOverride(false, address(_testPatchLiteRefNFT)); vm.stopPrank(); // try to transfer a patch directly - it should be blocked because it is soulbound - assertEq(address(7), testPatchLiteRefNFT.ownerOf(patchTokenId)); // Report as soulbound - vm.startPrank(userAddress); // Prank from underlying owner address - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TransferNotAllowed.selector, address(testPatchLiteRefNFT), patchTokenId)); - testPatchLiteRefNFT.transferFrom(userAddress, address(7), patchTokenId); + assertEq(address(7), _testPatchLiteRefNFT.ownerOf(patchTokenId)); // Report as soulbound + vm.startPrank(_userAddress); // Prank from underlying owner address + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TransferNotAllowed.selector, address(_testPatchLiteRefNFT), patchTokenId)); + _testPatchLiteRefNFT.transferFrom(_userAddress, address(7), patchTokenId); vm.stopPrank(); } function testUnassignMultiNFT() public { vm.expectRevert(); // not unassignable - prot.unassignMultiNFT(address(1), 1, address(1), 1); + _prot.unassignMultiNFT(address(1), 1, address(1), 1); - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - uint256 fragment1 = testMultiFragmentNFT.mint(userAddress); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + uint256 fragment1 = _testMultiFragmentNFT.mint(_userAddress); - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, false); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); - testPatchLiteRefNFT.registerReferenceAddress(address(testMultiFragmentNFT)); - prot.assignNFT(address(testMultiFragmentNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); - prot.unassignNFT(address(testMultiFragmentNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssignedToTarget.selector, address(testMultiFragmentNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId)); - prot.unassignNFT(address(testMultiFragmentNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); + _testPatchLiteRefNFT.registerReferenceAddress(address(_testMultiFragmentNFT)); + _prot.assignNFT(address(_testMultiFragmentNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + _prot.unassignNFT(address(_testMultiFragmentNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssignedToTarget.selector, address(_testMultiFragmentNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId)); + _prot.unassignNFT(address(_testMultiFragmentNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); } function testBatchAssignNFT() public { - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, false); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); address[] memory fragmentAddresses = new address[](8); uint256[] memory fragments = new uint256[](8); for (uint8 i = 0; i < 8; i++) { - fragmentAddresses[i] = address(testFragmentLiteRefNFT); - fragments[i] = testFragmentLiteRefNFT.mint(userAddress); + fragmentAddresses[i] = address(_testFragmentLiteRefNFT); + fragments[i] = _testFragmentLiteRefNFT.mint(_userAddress); } // Fragment must be registered - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(testFragmentLiteRefNFT))); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); - uint8 refId = testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testFragmentLiteRefNFT))); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + uint8 refId = _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); vm.stopPrank(); // User may not assign without userAssign enabled - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - vm.prank(userAddress); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); - vm.prank(scopeOwner); - prot.setScopeRules(scopeName, false, false, true); - vm.startPrank(scopeOwner); + vm.prank(_scopeOwner); + _prot.setScopeRules(_scopeName, false, false, true); + vm.startPrank(_scopeOwner); // Whitelist enabled requires whitelisted (both patch and fragment) - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, scopeName, address(testPatchLiteRefNFT))); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(_testPatchLiteRefNFT))); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); - prot.addWhitelist(scopeName, address(testPatchLiteRefNFT)); + _prot.addWhitelist(_scopeName, address(_testPatchLiteRefNFT)); // Whitelist enabled requires whitelisted (now just fragment) - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, scopeName, address(testFragmentLiteRefNFT))); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(_testFragmentLiteRefNFT))); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); - prot.addWhitelist(scopeName, address(testFragmentLiteRefNFT)); + _prot.addWhitelist(_scopeName, address(_testFragmentLiteRefNFT)); // Inputs do not match length vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.BadInputLengths.selector)); - prot.batchAssignNFT(new address[](1), fragments, address(testPatchLiteRefNFT), patchTokenId); + _prot.batchAssignNFT(new address[](1), fragments, address(_testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); - vm.prank(userAddress); - testPatchLiteRefNFT.setFrozen(patchTokenId, true); + vm.prank(_userAddress); + _testPatchLiteRefNFT.setFrozen(patchTokenId, true); // It's frozen (patch) - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testPatchLiteRefNFT), patchTokenId)); - vm.prank(scopeOwner); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); - vm.prank(userAddress); - testPatchLiteRefNFT.setFrozen(patchTokenId, false); - - vm.prank(userAddress); - testFragmentLiteRefNFT.setFrozen(fragments[0], true); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(_testPatchLiteRefNFT), patchTokenId)); + vm.prank(_scopeOwner); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + vm.prank(_userAddress); + _testPatchLiteRefNFT.setFrozen(patchTokenId, false); + + vm.prank(_userAddress); + _testFragmentLiteRefNFT.setFrozen(fragments[0], true); // It's frozen (fragment) - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragments[0])); - vm.prank(scopeOwner); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); - vm.prank(userAddress); - testFragmentLiteRefNFT.setFrozen(fragments[0], false); - - vm.prank(scopeOwner); - testPatchLiteRefNFT.redactReferenceAddress(refId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(_testFragmentLiteRefNFT), fragments[0])); + vm.prank(_scopeOwner); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + vm.prank(_userAddress); + _testFragmentLiteRefNFT.setFrozen(fragments[0], false); + + vm.prank(_scopeOwner); + _testPatchLiteRefNFT.redactReferenceAddress(refId); // Fragment was redacted - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentRedacted.selector, address(testFragmentLiteRefNFT))); - vm.prank(scopeOwner); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); - vm.prank(scopeOwner); - testPatchLiteRefNFT.unredactReferenceAddress(refId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentRedacted.selector, address(_testFragmentLiteRefNFT))); + vm.prank(_scopeOwner); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + vm.prank(_scopeOwner); + _testPatchLiteRefNFT.unredactReferenceAddress(refId); address[] memory selfAddr = new address[](1); uint256[] memory selfFrag = new uint256[](1); - selfAddr[0] = address(testPatchLiteRefNFT); + selfAddr[0] = address(_testPatchLiteRefNFT); selfFrag[0] = patchTokenId; // Self-assignment vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.SelfAssignmentNotAllowed.selector, selfAddr[0], selfFrag[0])); - vm.prank(scopeOwner); - prot.batchAssignNFT(selfAddr, selfFrag, address(testPatchLiteRefNFT), patchTokenId); + vm.prank(_scopeOwner); + _prot.batchAssignNFT(selfAddr, selfFrag, address(_testPatchLiteRefNFT), patchTokenId); - vm.prank(userAddress); - testFragmentLiteRefNFT.setLocked(fragments[0], true); + vm.prank(_userAddress); + _testFragmentLiteRefNFT.setLocked(fragments[0], true); // Fragment is locked - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Locked.selector, address(testFragmentLiteRefNFT), fragments[0])); - vm.prank(scopeOwner); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); - vm.prank(userAddress); - testFragmentLiteRefNFT.setLocked(fragments[0], false); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Locked.selector, address(_testFragmentLiteRefNFT), fragments[0])); + vm.prank(_scopeOwner); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + vm.prank(_userAddress); + _testFragmentLiteRefNFT.setLocked(fragments[0], false); // test assigning fragments for another user address[] memory otherUserAddr = new address[](1); uint256[] memory otherUserFrag = new uint256[](1); - otherUserAddr[0] = address(testFragmentLiteRefNFT); - otherUserFrag[0] = testFragmentLiteRefNFT.mint(user2Address); + otherUserAddr[0] = address(_testFragmentLiteRefNFT); + otherUserFrag[0] = _testFragmentLiteRefNFT.mint(_user2Address); // Target and fragment not same owner - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, scopeOwner)); - vm.prank(scopeOwner); - prot.batchAssignNFT(otherUserAddr, otherUserFrag, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); + vm.prank(_scopeOwner); + _prot.batchAssignNFT(otherUserAddr, otherUserFrag, address(_testPatchLiteRefNFT), patchTokenId); // finally a positive test case - vm.prank(scopeOwner); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); + vm.prank(_scopeOwner); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); for (uint8 i = 0; i < 8; i++) { - (address addr, uint256 tokenId) = testFragmentLiteRefNFT.getAssignedTo(fragments[i]); - assertEq(addr, address(testPatchLiteRefNFT)); + (address addr, uint256 tokenId) = _testFragmentLiteRefNFT.getAssignedTo(fragments[i]); + assertEq(addr, address(_testPatchLiteRefNFT)); assertEq(tokenId, patchTokenId); - assertEq(testFragmentLiteRefNFT.ownerOf(fragments[i]), userAddress); - testFragmentLiteRefNFT.setTestLockOverride(true); // setup for next test part + assertEq(_testFragmentLiteRefNFT.ownerOf(fragments[i]), _userAddress); + _testFragmentLiteRefNFT.setTestLockOverride(true); // setup for next test part } vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, fragmentAddresses[0], fragments[0])); - vm.prank(scopeOwner); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); + vm.prank(_scopeOwner); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); } function testBatchUserPatchAssignNFT() public { - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, true, true, false); - testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, true, true, false); + _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); vm.stopPrank(); - vm.startPrank(userAddress); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); + vm.startPrank(_userAddress); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); address[] memory fragmentAddresses = new address[](8); uint256[] memory fragments = new uint256[](8); uint256[] memory user2Fragments = new uint256[](8); for (uint8 i = 0; i < 8; i++) { - fragmentAddresses[i] = address(testFragmentLiteRefNFT); - fragments[i] = testFragmentLiteRefNFT.mint(userAddress); - user2Fragments[i] = testFragmentLiteRefNFT.mint(user2Address); + fragmentAddresses[i] = address(_testFragmentLiteRefNFT); + fragments[i] = _testFragmentLiteRefNFT.mint(_userAddress); + user2Fragments[i] = _testFragmentLiteRefNFT.mint(_user2Address); } vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); - vm.prank(user2Address); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + vm.prank(_user2Address); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); - vm.prank(user2Address); - prot.batchAssignNFT(fragmentAddresses, user2Fragments, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + vm.prank(_user2Address); + _prot.batchAssignNFT(fragmentAddresses, user2Fragments, address(_testPatchLiteRefNFT), patchTokenId); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - vm.prank(userAddress); - prot.batchAssignNFT(fragmentAddresses, user2Fragments, address(testPatchLiteRefNFT), patchTokenId); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); + _prot.batchAssignNFT(fragmentAddresses, user2Fragments, address(_testPatchLiteRefNFT), patchTokenId); // test assigning fragments for another user address[] memory otherUserAddr = new address[](1); uint256[] memory otherUserFrag = new uint256[](1); - otherUserAddr[0] = address(testFragmentLiteRefNFT); - otherUserFrag[0] = testFragmentLiteRefNFT.mint(user2Address); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, userAddress)); - vm.prank(userAddress); - prot.batchAssignNFT(otherUserAddr, otherUserFrag, address(testPatchLiteRefNFT), patchTokenId); + otherUserAddr[0] = address(_testFragmentLiteRefNFT); + otherUserFrag[0] = _testFragmentLiteRefNFT.mint(_user2Address); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); + _prot.batchAssignNFT(otherUserAddr, otherUserFrag, address(_testPatchLiteRefNFT), patchTokenId); // finally a positive test case - vm.startPrank(userAddress); - prot.batchAssignNFT(fragmentAddresses, fragments, address(testPatchLiteRefNFT), patchTokenId); + vm.startPrank(_userAddress); + _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); for (uint8 i = 0; i < 8; i++) { - (address addr, uint256 tokenId) = testFragmentLiteRefNFT.getAssignedTo(fragments[i]); - assertEq(addr, address(testPatchLiteRefNFT)); + (address addr, uint256 tokenId) = _testFragmentLiteRefNFT.getAssignedTo(fragments[i]); + assertEq(addr, address(_testPatchLiteRefNFT)); assertEq(tokenId, patchTokenId); - assertEq(testFragmentLiteRefNFT.ownerOf(fragments[i]), userAddress); + assertEq(_testFragmentLiteRefNFT.ownerOf(fragments[i]), _userAddress); } vm.stopPrank(); } function testTransferLogs() public { - uint256 fragment1 = testFragmentLiteRefNFT.mint(userAddress); - uint256 fragment2 = testFragmentLiteRefNFT.mint(userAddress); - uint256 fragment3 = testFragmentLiteRefNFT.mint(userAddress); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment3 = _testFragmentLiteRefNFT.mint(_userAddress); - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, false); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); - //Register testFragmentLiteRefNFT to testFragmentLiteRefNFT to allow recursion - testFragmentLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + //Register _testFragmentLiteRefNFT to _testFragmentLiteRefNFT to allow recursion + _testFragmentLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); // Assign Id2 -> Id1 - prot.assignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), fragment1); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); // Assign Id3 -> Id2 - prot.assignNFT(address(testFragmentLiteRefNFT), fragment3, address(testFragmentLiteRefNFT), fragment2); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment3, address(_testFragmentLiteRefNFT), fragment2); vm.stopPrank(); - vm.expectEmit(true, true, true, true, address(testFragmentLiteRefNFT)); - emit Transfer(userAddress, user2Address, fragment2); - vm.expectEmit(true, true, true, true, address(testFragmentLiteRefNFT)); - emit Transfer(userAddress, user2Address, fragment3); - vm.expectEmit(true, true, true, true, address(testFragmentLiteRefNFT)); - emit Transfer(userAddress, user2Address, fragment1); - vm.prank(userAddress); - testFragmentLiteRefNFT.transferFrom(userAddress, user2Address, fragment1); - assertEq(user2Address, testFragmentLiteRefNFT.ownerOf(fragment1)); - assertEq(user2Address, testFragmentLiteRefNFT.ownerOf(fragment2)); - assertEq(user2Address, testFragmentLiteRefNFT.ownerOf(fragment3)); + vm.expectEmit(true, true, true, true, address(_testFragmentLiteRefNFT)); + emit Transfer(_userAddress, _user2Address, fragment2); + vm.expectEmit(true, true, true, true, address(_testFragmentLiteRefNFT)); + emit Transfer(_userAddress, _user2Address, fragment3); + vm.expectEmit(true, true, true, true, address(_testFragmentLiteRefNFT)); + emit Transfer(_userAddress, _user2Address, fragment1); + vm.prank(_userAddress); + _testFragmentLiteRefNFT.transferFrom(_userAddress, _user2Address, fragment1); + assertEq(_user2Address, _testFragmentLiteRefNFT.ownerOf(fragment1)); + assertEq(_user2Address, _testFragmentLiteRefNFT.ownerOf(fragment2)); + assertEq(_user2Address, _testFragmentLiteRefNFT.ownerOf(fragment3)); } function testUpdateOwnership() public { - uint256 fragment1 = testFragmentLiteRefNFT.mint(userAddress); - uint256 fragment2 = testFragmentLiteRefNFT.mint(userAddress); - uint256 fragment3 = testFragmentLiteRefNFT.mint(userAddress); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment3 = _testFragmentLiteRefNFT.mint(_userAddress); - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, false); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); - //Register testFragmentLiteRefNFT to testFragmentLiteRefNFT to allow recursion - testFragmentLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + //Register _testFragmentLiteRefNFT to _testFragmentLiteRefNFT to allow recursion + _testFragmentLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); // Assign Id2 -> Id1 - prot.assignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), fragment1); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); // Assign Id3 -> Id2 - prot.assignNFT(address(testFragmentLiteRefNFT), fragment3, address(testFragmentLiteRefNFT), fragment2); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment3, address(_testFragmentLiteRefNFT), fragment2); vm.stopPrank(); - vm.prank(userAddress); + vm.prank(_userAddress); // This won't actually transfer fragments 2 and 3 - testFragmentLiteRefNFT.transferFrom(userAddress, user2Address, fragment1); - assertEq(user2Address, testFragmentLiteRefNFT.unassignedOwnerOf(fragment1)); - assertEq(userAddress, testFragmentLiteRefNFT.unassignedOwnerOf(fragment2)); - assertEq(userAddress, testFragmentLiteRefNFT.unassignedOwnerOf(fragment3)); - prot.updateOwnershipTree(address(testFragmentLiteRefNFT), fragment1); - assertEq(user2Address, testFragmentLiteRefNFT.unassignedOwnerOf(fragment1)); - assertEq(user2Address, testFragmentLiteRefNFT.unassignedOwnerOf(fragment2)); - assertEq(user2Address, testFragmentLiteRefNFT.unassignedOwnerOf(fragment3)); + _testFragmentLiteRefNFT.transferFrom(_userAddress, _user2Address, fragment1); + assertEq(_user2Address, _testFragmentLiteRefNFT.unassignedOwnerOf(fragment1)); + assertEq(_userAddress, _testFragmentLiteRefNFT.unassignedOwnerOf(fragment2)); + assertEq(_userAddress, _testFragmentLiteRefNFT.unassignedOwnerOf(fragment3)); + _prot.updateOwnershipTree(address(_testFragmentLiteRefNFT), fragment1); + assertEq(_user2Address, _testFragmentLiteRefNFT.unassignedOwnerOf(fragment1)); + assertEq(_user2Address, _testFragmentLiteRefNFT.unassignedOwnerOf(fragment2)); + assertEq(_user2Address, _testFragmentLiteRefNFT.unassignedOwnerOf(fragment3)); // test with patch - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - vm.prank(scopeOwner); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); - vm.prank(userAddress); - testBaseNFT.transferFrom(userAddress, user2Address, testBaseNFTTokenId); - assertEq(user2Address, testPatchLiteRefNFT.ownerOf(patchTokenId)); - assertEq(userAddress, testPatchLiteRefNFT.unpatchedOwnerOf(patchTokenId)); - prot.updateOwnershipTree(address(testPatchLiteRefNFT), patchTokenId); - assertEq(user2Address, testPatchLiteRefNFT.unpatchedOwnerOf(patchTokenId)); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + vm.prank(_scopeOwner); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + vm.prank(_userAddress); + _testBaseNFT.transferFrom(_userAddress, _user2Address, _testBaseNFTTokenId); + assertEq(_user2Address, _testPatchLiteRefNFT.ownerOf(patchTokenId)); + assertEq(_userAddress, _testPatchLiteRefNFT.unpatchedOwnerOf(patchTokenId)); + _prot.updateOwnershipTree(address(_testPatchLiteRefNFT), patchTokenId); + assertEq(_user2Address, _testPatchLiteRefNFT.unpatchedOwnerOf(patchTokenId)); } function testLocks() public { - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - uint256 fragment1 = testFragmentLiteRefNFT.mint(userAddress); - uint256 fragment2 = testFragmentLiteRefNFT.mint(userAddress); - testPatchworkNFT.mint(userAddress, 1); - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, false); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); - - testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - //Register testFragmentLiteRefNFT to testFragmentLiteRefNFT to allow recursion - testFragmentLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress); + _testPatchworkNFT.mint(_userAddress, 1); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + + _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); + //Register _testFragmentLiteRefNFT to _testFragmentLiteRefNFT to allow recursion + _testFragmentLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); vm.stopPrank(); // cannot lock a patch - assertEq(false, testPatchLiteRefNFT.locked(patchTokenId)); + assertEq(false, _testPatchLiteRefNFT.locked(patchTokenId)); vm.expectRevert(); - vm.prank(userAddress); - testPatchLiteRefNFT.setLocked(patchTokenId, true); + vm.prank(_userAddress); + _testPatchLiteRefNFT.setLocked(patchTokenId, true); // can lock an unassigned fragment - assertEq(false, testFragmentLiteRefNFT.locked(fragment1)); - vm.prank(userAddress); - testFragmentLiteRefNFT.setLocked(fragment1, true); - assertEq(true, testFragmentLiteRefNFT.locked(fragment1)); - vm.prank(userAddress); - testFragmentLiteRefNFT.setLocked(fragment1, false); + assertEq(false, _testFragmentLiteRefNFT.locked(fragment1)); + vm.prank(_userAddress); + _testFragmentLiteRefNFT.setLocked(fragment1, true); + assertEq(true, _testFragmentLiteRefNFT.locked(fragment1)); + vm.prank(_userAddress); + _testFragmentLiteRefNFT.setLocked(fragment1, false); // only owner may lock - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); - vm.prank(user2Address); - testFragmentLiteRefNFT.setLocked(fragment1, true); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + vm.prank(_user2Address); + _testFragmentLiteRefNFT.setLocked(fragment1, true); // only owner may lock - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, user2Address)); - vm.prank(user2Address); - testPatchworkNFT.setLocked(1, true); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + vm.prank(_user2Address); + _testPatchworkNFT.setLocked(1, true); // an assigned fragment is locked implicitly - assertEq(false, testFragmentLiteRefNFT.locked(fragment1)); - vm.prank(scopeOwner); + assertEq(false, _testFragmentLiteRefNFT.locked(fragment1)); + vm.prank(_scopeOwner); // Assign Id1 -> Id - prot.assignNFT(address(testFragmentLiteRefNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); - assertEq(true, testFragmentLiteRefNFT.locked(fragment1)); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + assertEq(true, _testFragmentLiteRefNFT.locked(fragment1)); // cannot lock an assigned fragment vm.expectRevert(); - vm.prank(userAddress); - testFragmentLiteRefNFT.setLocked(fragment1, true); + vm.prank(_userAddress); + _testFragmentLiteRefNFT.setLocked(fragment1, true); // cannot assign a locked fragment - vm.startPrank(userAddress); - testFragmentLiteRefNFT.setLocked(fragment2, true); + vm.startPrank(_userAddress); + _testFragmentLiteRefNFT.setLocked(fragment2, true); vm.expectRevert(); - prot.assignNFT(address(testFragmentLiteRefNFT), fragment2, address(testPatchLiteRefNFT), patchTokenId); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testPatchLiteRefNFT), patchTokenId); // cannot transfer a locked fragment vm.expectRevert(); - testFragmentLiteRefNFT.transferFrom(userAddress, address(100), fragment2); - testFragmentLiteRefNFT.setLocked(fragment2, false); - testFragmentLiteRefNFT.transferFrom(userAddress, address(100), fragment2); - assertEq(address(100), testFragmentLiteRefNFT.ownerOf(fragment2)); + _testFragmentLiteRefNFT.transferFrom(_userAddress, address(100), fragment2); + _testFragmentLiteRefNFT.setLocked(fragment2, false); + _testFragmentLiteRefNFT.transferFrom(_userAddress, address(100), fragment2); + assertEq(address(100), _testFragmentLiteRefNFT.ownerOf(fragment2)); vm.stopPrank(); } function testFreezes() public { - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - uint256 fragment1 = testFragmentLiteRefNFT.mint(userAddress); - uint256 fragment2 = testFragmentLiteRefNFT.mint(userAddress); - - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, false); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); - - testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - //Register testFragmentLiteRefNFT to testFragmentLiteRefNFT to allow recursion - testFragmentLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress); + + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + + _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); + //Register _testFragmentLiteRefNFT to _testFragmentLiteRefNFT to allow recursion + _testFragmentLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); vm.stopPrank(); // Freeze the patch, shouldn't allow assignment of a child - vm.prank(userAddress); - testPatchLiteRefNFT.setFrozen(patchTokenId, true); - assertEq(0, testPatchLiteRefNFT.getFreezeNonce(patchTokenId)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testPatchLiteRefNFT), patchTokenId)); - vm.prank(scopeOwner); + vm.prank(_userAddress); + _testPatchLiteRefNFT.setFrozen(patchTokenId, true); + assertEq(0, _testPatchLiteRefNFT.getFreezeNonce(patchTokenId)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(_testPatchLiteRefNFT), patchTokenId)); + vm.prank(_scopeOwner); // Assign Id1 -> Id - prot.assignNFT(address(testFragmentLiteRefNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); - vm.prank(userAddress); - testPatchLiteRefNFT.setFrozen(patchTokenId, false); - vm.startPrank(scopeOwner); - assertEq(1, testPatchLiteRefNFT.getFreezeNonce(patchTokenId)); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + vm.prank(_userAddress); + _testPatchLiteRefNFT.setFrozen(patchTokenId, false); + vm.startPrank(_scopeOwner); + assertEq(1, _testPatchLiteRefNFT.getFreezeNonce(patchTokenId)); // Assign Id1 -> Id - prot.assignNFT(address(testFragmentLiteRefNFT), fragment1, address(testPatchLiteRefNFT), patchTokenId); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); // Assign Id2 -> Id1 - prot.assignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), fragment1); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); // Lock the patch, shouldn't allow unassignment of any child vm.stopPrank(); - vm.prank(userAddress); - testPatchLiteRefNFT.setFrozen(patchTokenId, true); + vm.prank(_userAddress); + _testPatchLiteRefNFT.setFrozen(patchTokenId, true); // It will return that the fragment is frozen even though the patch is the root cause, because all assigned to the patch inherit the freeze - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragment2)); - vm.prank(scopeOwner); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(_testFragmentLiteRefNFT), fragment2)); + vm.prank(_scopeOwner); // Now Id2 -> Id1 -> Id, unassign Id2 from Id1 - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); - vm.prank(userAddress); - testPatchLiteRefNFT.setFrozen(patchTokenId, false); - assertEq(2, testPatchLiteRefNFT.getFreezeNonce(patchTokenId)); - vm.startPrank(scopeOwner); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); + vm.prank(_userAddress); + _testPatchLiteRefNFT.setFrozen(patchTokenId, false); + assertEq(2, _testPatchLiteRefNFT.getFreezeNonce(patchTokenId)); + vm.startPrank(_scopeOwner); // Now Id2 -> Id1 -> Id, unassign Id2 from Id1 - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment2); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); vm.stopPrank(); - vm.startPrank(scopeOwner); + vm.startPrank(_scopeOwner); // Unassign Id1 from patch - prot.unassignSingleNFT(address(testFragmentLiteRefNFT), fragment1); + _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment1); vm.stopPrank(); // Lock the fragment, shouldn't allow assignment to anything - vm.prank(userAddress); - testFragmentLiteRefNFT.setFrozen(fragment1, true); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragment1)); - vm.prank(scopeOwner); - prot.assignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), fragment1); - vm.prank(userAddress); - testFragmentLiteRefNFT.setFrozen(fragment2, true); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(testFragmentLiteRefNFT), fragment1)); - vm.prank(scopeOwner); - prot.assignNFT(address(testFragmentLiteRefNFT), fragment2, address(testFragmentLiteRefNFT), fragment1); - vm.startPrank(userAddress); - testFragmentLiteRefNFT.setFrozen(fragment2, false); + vm.prank(_userAddress); + _testFragmentLiteRefNFT.setFrozen(fragment1, true); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(_testFragmentLiteRefNFT), fragment1)); + vm.prank(_scopeOwner); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); + vm.prank(_userAddress); + _testFragmentLiteRefNFT.setFrozen(fragment2, true); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(_testFragmentLiteRefNFT), fragment1)); + vm.prank(_scopeOwner); + _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); + vm.startPrank(_userAddress); + _testFragmentLiteRefNFT.setFrozen(fragment2, false); // test transfers with lock nonce mismatch // first unassign the fragment b/c you can't unassign a locked one - uint256 nonce = testFragmentLiteRefNFT.getFreezeNonce(fragment2); - testFragmentLiteRefNFT.setFrozen(fragment2, true); - testFragmentLiteRefNFT.setFrozen(fragment2, false); // nonce +1 - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, address(testFragmentLiteRefNFT), fragment2)); - testFragmentLiteRefNFT.transferFromWithFreezeNonce(userAddress, user2Address, fragment2, nonce+1); - assertEq(false, testFragmentLiteRefNFT.frozen(fragment2)); - testFragmentLiteRefNFT.setFrozen(fragment2, true); - assertEq(true, testFragmentLiteRefNFT.frozen(fragment2)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, address(testFragmentLiteRefNFT), fragment2, nonce)); - testFragmentLiteRefNFT.transferFromWithFreezeNonce(userAddress, user2Address, fragment2, nonce); + uint256 nonce = _testFragmentLiteRefNFT.getFreezeNonce(fragment2); + _testFragmentLiteRefNFT.setFrozen(fragment2, true); + _testFragmentLiteRefNFT.setFrozen(fragment2, false); // nonce +1 + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, address(_testFragmentLiteRefNFT), fragment2)); + _testFragmentLiteRefNFT.transferFromWithFreezeNonce(_userAddress, _user2Address, fragment2, nonce+1); + assertEq(false, _testFragmentLiteRefNFT.frozen(fragment2)); + _testFragmentLiteRefNFT.setFrozen(fragment2, true); + assertEq(true, _testFragmentLiteRefNFT.frozen(fragment2)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, address(_testFragmentLiteRefNFT), fragment2, nonce)); + _testFragmentLiteRefNFT.transferFromWithFreezeNonce(_userAddress, _user2Address, fragment2, nonce); // now success - testFragmentLiteRefNFT.transferFromWithFreezeNonce(userAddress, user2Address, fragment2, nonce+1); - assertEq(user2Address, testFragmentLiteRefNFT.ownerOf(fragment2)); + _testFragmentLiteRefNFT.transferFromWithFreezeNonce(_userAddress, _user2Address, fragment2, nonce+1); + assertEq(_user2Address, _testFragmentLiteRefNFT.ownerOf(fragment2)); } function testSpoofedTransfer1() public { - vm.startPrank(scopeOwner); + vm.startPrank(_scopeOwner); // create a patchworkliteref but manually put in an entry that isn't assigned to it (spoof ownership) - uint256 fragment1 = testFragmentLiteRefNFT.mint(userAddress); - uint256 fragment2 = testFragmentLiteRefNFT.mint(userAddress); - testFragmentLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - (uint64 ref, ) = testFragmentLiteRefNFT.getLiteReference(address(testFragmentLiteRefNFT), fragment2); - testFragmentLiteRefNFT.addReference(fragment1, ref); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress); + _testFragmentLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); + (uint64 ref, ) = _testFragmentLiteRefNFT.getLiteReference(address(_testFragmentLiteRefNFT), fragment2); + _testFragmentLiteRefNFT.addReference(fragment1, ref); // Should revert with data integrity error vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.DataIntegrityError.selector, address(testFragmentLiteRefNFT), fragment1, address(0), 0)); - vm.prank(userAddress); - testFragmentLiteRefNFT.transferFrom(userAddress, user2Address, fragment1); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.DataIntegrityError.selector, address(_testFragmentLiteRefNFT), fragment1, address(0), 0)); + vm.prank(_userAddress); + _testFragmentLiteRefNFT.transferFrom(_userAddress, _user2Address, fragment1); } function testSpoofedTransfer2() public { - vm.startPrank(scopeOwner); + vm.startPrank(_scopeOwner); // create a patchworkliteref but manually put in an entry that isn't assigned to it (spoof ownership) - uint256 fragment1 = testFragmentLiteRefNFT.mint(userAddress); - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - testFragmentLiteRefNFT.registerReferenceAddress(address(testBaseNFT)); - (uint64 ref, ) = testFragmentLiteRefNFT.getLiteReference(address(testBaseNFT), testBaseNFTTokenId); - testFragmentLiteRefNFT.addReference(fragment1, ref); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + _testFragmentLiteRefNFT.registerReferenceAddress(address(_testBaseNFT)); + (uint64 ref, ) = _testFragmentLiteRefNFT.getLiteReference(address(_testBaseNFT), _testBaseNFTTokenId); + _testFragmentLiteRefNFT.addReference(fragment1, ref); // Should revert with data integrity error vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotPatchworkAssignable.selector, address(testBaseNFT))); - vm.prank(userAddress); - testFragmentLiteRefNFT.transferFrom(userAddress, user2Address, fragment1); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotPatchworkAssignable.selector, address(_testBaseNFT))); + vm.prank(_userAddress); + _testFragmentLiteRefNFT.transferFrom(_userAddress, _user2Address, fragment1); } function testLiteRefCollision() public { - TestFragmentLiteRefNFT testFrag2 = new TestFragmentLiteRefNFT(address(prot)); - vm.startPrank(scopeOwner); - prot.claimScope(scopeName); - prot.setScopeRules(scopeName, false, false, false); - testPatchLiteRefNFT.registerReferenceAddress(address(testFragmentLiteRefNFT)); - testFragmentLiteRefNFT.registerReferenceAddress(address(testFrag2)); - uint256 frag1 = testFragmentLiteRefNFT.mint(userAddress); - uint256 frag2 = testFrag2.mint(userAddress); - uint256 testBaseNFTTokenId = testBaseNFT.mint(userAddress); - uint256 patchTokenId = prot.createPatch(address(testBaseNFT), testBaseNFTTokenId, address(testPatchLiteRefNFT)); - prot.assignNFT(address(testFragmentLiteRefNFT), frag1, address(testPatchLiteRefNFT), patchTokenId); + TestFragmentLiteRefNFT testFrag2 = new TestFragmentLiteRefNFT(address(_prot)); + vm.startPrank(_scopeOwner); + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); + _testFragmentLiteRefNFT.registerReferenceAddress(address(testFrag2)); + uint256 frag1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 frag2 = testFrag2.mint(_userAddress); + uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); + uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + _prot.assignNFT(address(_testFragmentLiteRefNFT), frag1, address(_testPatchLiteRefNFT), patchTokenId); // The second assign succeeding combined with the assertion that they are equal ref values means there is no collision in the scope. - prot.assignNFT(address(testFrag2), frag2, address(testFragmentLiteRefNFT), frag1); + _prot.assignNFT(address(testFrag2), frag2, address(_testFragmentLiteRefNFT), frag1); // LiteRef IDs should match because it is idx1 tokenID 0 for both (0x1. 0x0) - (uint64 lr1,) = testPatchLiteRefNFT.getLiteReference(address(testFragmentLiteRefNFT), frag1); - (uint64 lr2,) = testFragmentLiteRefNFT.getLiteReference(address(testFrag2), frag2); + (uint64 lr1,) = _testPatchLiteRefNFT.getLiteReference(address(_testFragmentLiteRefNFT), frag1); + (uint64 lr2,) = _testFragmentLiteRefNFT.getLiteReference(address(testFrag2), frag2); assertEq(lr1, lr2); } } \ No newline at end of file diff --git a/test/TestPatchLiteRefNFT.t.sol b/test/TestPatchLiteRefNFT.t.sol index 33e13e5..c88e937 100644 --- a/test/TestPatchLiteRefNFT.t.sol +++ b/test/TestPatchLiteRefNFT.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import "../src/sampleNFTs/TestPatchLiteRefNFT.sol"; +import "./nfts/TestPatchLiteRefNFT.sol"; contract TestPatchLiteRefNFTTest is Test { TestPatchLiteRefNFT testNFT; diff --git a/src/sampleNFTs/TestAccountPatchNFT.sol b/test/nfts/TestAccountPatchNFT.sol similarity index 97% rename from src/sampleNFTs/TestAccountPatchNFT.sol rename to test/nfts/TestAccountPatchNFT.sol index 43278fb..e225ae5 100644 --- a/src/sampleNFTs/TestAccountPatchNFT.sol +++ b/test/nfts/TestAccountPatchNFT.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "../PatchworkAccountPatch.sol"; +import "../../src/PatchworkAccountPatch.sol"; contract TestAccountPatchNFT is PatchworkAccountPatch { diff --git a/src/sampleNFTs/TestBaseNFT.sol b/test/nfts/TestBaseNFT.sol similarity index 100% rename from src/sampleNFTs/TestBaseNFT.sol rename to test/nfts/TestBaseNFT.sol diff --git a/src/sampleNFTs/TestFragmentLiteRefNFT.sol b/test/nfts/TestFragmentLiteRefNFT.sol similarity index 99% rename from src/sampleNFTs/TestFragmentLiteRefNFT.sol rename to test/nfts/TestFragmentLiteRefNFT.sol index d8daf83..2c19712 100644 --- a/src/sampleNFTs/TestFragmentLiteRefNFT.sol +++ b/test/nfts/TestFragmentLiteRefNFT.sol @@ -9,8 +9,8 @@ pragma solidity ^0.8.13; Has metadata as defined in totem-metadata.json */ -import "../PatchworkFragmentSingle.sol"; -import "../PatchworkLiteRef.sol"; +import "../../src/PatchworkFragmentSingle.sol"; +import "../../src/PatchworkLiteRef.sol"; enum FragmentType { BASE, diff --git a/src/sampleNFTs/TestMultiFragmentNFT.sol b/test/nfts/TestMultiFragmentNFT.sol similarity index 96% rename from src/sampleNFTs/TestMultiFragmentNFT.sol rename to test/nfts/TestMultiFragmentNFT.sol index 2268643..dbc79de 100644 --- a/src/sampleNFTs/TestMultiFragmentNFT.sol +++ b/test/nfts/TestMultiFragmentNFT.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "../PatchworkFragmentMulti.sol"; -import "../PatchworkLiteRef.sol"; +import "../../src/PatchworkFragmentMulti.sol"; +import "../../src/PatchworkLiteRef.sol"; struct TestMultiFragmentNFTMetadata { uint8 nothing; diff --git a/src/sampleNFTs/TestPatchFragmentNFT.sol b/test/nfts/TestPatchFragmentNFT.sol similarity index 98% rename from src/sampleNFTs/TestPatchFragmentNFT.sol rename to test/nfts/TestPatchFragmentNFT.sol index e706cf7..ca80d60 100644 --- a/src/sampleNFTs/TestPatchFragmentNFT.sol +++ b/test/nfts/TestPatchFragmentNFT.sol @@ -9,8 +9,8 @@ pragma solidity ^0.8.13; Has metadata as defined in totem-metadata.json */ -import "../PatchworkPatch.sol"; -import "../PatchworkFragmentSingle.sol"; +import "../../src/PatchworkPatch.sol"; +import "../../src/PatchworkFragmentSingle.sol"; struct TestPatchFragmentNFTMetadata { uint16 xp; diff --git a/src/sampleNFTs/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol similarity index 99% rename from src/sampleNFTs/TestPatchLiteRefNFT.sol rename to test/nfts/TestPatchLiteRefNFT.sol index 4f2dd77..3964f15 100644 --- a/src/sampleNFTs/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -9,8 +9,8 @@ pragma solidity ^0.8.13; Has metadata as defined in totem-metadata.json */ -import "../PatchworkPatch.sol"; -import "../PatchworkLiteRef.sol"; +import "../../src/PatchworkPatch.sol"; +import "../../src/PatchworkLiteRef.sol"; struct TestPatchLiteRefNFTMetadata { uint64[8] artifactIDs; diff --git a/src/sampleNFTs/TestPatchworkNFT.sol b/test/nfts/TestPatchworkNFT.sol similarity index 96% rename from src/sampleNFTs/TestPatchworkNFT.sol rename to test/nfts/TestPatchworkNFT.sol index 44d8263..6bd0b4a 100644 --- a/src/sampleNFTs/TestPatchworkNFT.sol +++ b/test/nfts/TestPatchworkNFT.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "../PatchworkNFT.sol"; +import "../../src/PatchworkNFT.sol"; contract TestPatchworkNFT is PatchworkNFT { From 4121af9e49b413e6c7737d2aaf6c0c2f06ee743a Mon Sep 17 00:00:00 2001 From: Rob Green Date: Mon, 6 Nov 2023 14:24:20 -0800 Subject: [PATCH 16/63] Explicit patch ownership (#37) * Allow any ownership of a patch * Coverage --- src/IPatchworkProtocol.sol | 4 +++- src/PatchworkPatch.sol | 1 + src/PatchworkProtocol.sol | 9 ++++---- test/PatchworkNFTReferences.t.sol | 6 ++--- test/PatchworkPatch.t.sol | 17 ++++++++++---- test/PatchworkProtocol.t.sol | 38 +++++++++++++++---------------- test/nfts/TestPatchLiteRefNFT.sol | 8 +++++-- 7 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index 3dec7a8..28550fc 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -455,15 +455,17 @@ interface IPatchworkProtocol { /** @notice Create a new patch + @param owner The owner of the patch @param originalNFTAddress Address of the original NFT @param originalNFTTokenId Token ID of the original NFT @param patchAddress Address of the IPatchworkPatch to mint @return tokenId Token ID of the newly created patch */ - function createPatch(address originalNFTAddress, uint originalNFTTokenId, address patchAddress) external returns (uint256 tokenId); + function createPatch(address owner, address originalNFTAddress, uint originalNFTTokenId, address patchAddress) external returns (uint256 tokenId); /** @notice Create a new account patch + @param owner The owner of the patch @param originalAddress Address of the original account @param patchAddress Address of the IPatchworkPatch to mint @return tokenId Token ID of the newly created patch diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index 21cdf1d..b9cde07 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -38,6 +38,7 @@ abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { @dev See {IERC721-ownerOf} */ function ownerOf(uint256 tokenId) public view virtual override(ERC721, IERC721) returns (address) { + // Default is inherited ownership return IERC721(_patchedAddresses[tokenId]).ownerOf(_patchedTokenIds[tokenId]); } diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 04ee4de..d97a5d4 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -146,16 +146,15 @@ contract PatchworkProtocol is IPatchworkProtocol { /** @dev See {IPatchworkProtocol-createAccountPatch} */ - function createPatch(address originalNFTAddress, uint originalNFTTokenId, address patchAddress) public returns (uint256 tokenId) { + function createPatch(address owner, address originalNFTAddress, uint originalNFTTokenId, address patchAddress) public returns (uint256 tokenId) { IPatchworkPatch patch = IPatchworkPatch(patchAddress); string memory scopeName = patch.getScopeName(); // mint a Patch that is soulbound to the originalNFT using the contract address at patchAddress which must support Patchwork metadata Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, patchAddress); - address tokenOwner = IERC721(originalNFTAddress).ownerOf(originalNFTTokenId); if (scope.owner == msg.sender || scope.operators[msg.sender]) { // continue - } else if (scope.allowUserPatch && msg.sender == tokenOwner) { + } else if (scope.allowUserPatch) { // continue } else { revert NotAuthorized(msg.sender); @@ -166,8 +165,8 @@ contract PatchworkProtocol is IPatchworkProtocol { revert AlreadyPatched(originalNFTAddress, originalNFTTokenId, patchAddress); } scope.uniquePatches[_hash] = true; - tokenId = patch.mintPatch(tokenOwner, originalNFTAddress, originalNFTTokenId); - emit Patch(tokenOwner, originalNFTAddress, originalNFTTokenId, patchAddress, tokenId); + tokenId = patch.mintPatch(owner, originalNFTAddress, originalNFTTokenId); + emit Patch(owner, originalNFTAddress, originalNFTTokenId, patchAddress, tokenId); return tokenId; } diff --git a/test/PatchworkNFTReferences.t.sol b/test/PatchworkNFTReferences.t.sol index 229d9f2..87293e7 100644 --- a/test/PatchworkNFTReferences.t.sol +++ b/test/PatchworkNFTReferences.t.sol @@ -68,12 +68,12 @@ contract PatchworkNFTCombinedTest is Test { assertEq(_userAddress, _testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); // TODO why doesn't this cover the branch != address(0) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); vm.prank(_user2Address); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); // must have user patch enabled - patchTokenId = _prot.createPatch(address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); vm.prank(_scopeOwner); - patchTokenId = _prot.createPatch(address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); // can't call directly _testFragmentLiteRefNFT.assign(fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); diff --git a/test/PatchworkPatch.t.sol b/test/PatchworkPatch.t.sol index a297b6d..415a6d5 100644 --- a/test/PatchworkPatch.t.sol +++ b/test/PatchworkPatch.t.sol @@ -59,7 +59,7 @@ contract PatchworkPatchTest is Test { function testLocks() public { uint256 baseTokenId = _testBaseNFT.mint(_userAddress); vm.prank(_scopeOwner); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); bool locked = _testPatchLiteRefNFT.locked(patchTokenId); assertFalse(locked); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.CannotLockSoulboundPatch.selector, _testPatchLiteRefNFT)); @@ -69,11 +69,18 @@ contract PatchworkPatchTest is Test { function testBurn() public { uint256 baseTokenId = _testBaseNFT.mint(_userAddress); vm.prank(_scopeOwner); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedOperation.selector)); _testPatchLiteRefNFT.burn(patchTokenId); } + function testOtherOwnerDisallowed() public { + uint256 baseTokenId = _testBaseNFT.mint(_userAddress); + vm.prank(_scopeOwner); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + _prot.createPatch(_user2Address, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + } + function testPatchFragment() public { vm.startPrank(_scopeOwner); uint256 baseTokenId = _testBaseNFT.mint(_userAddress); @@ -81,9 +88,9 @@ contract PatchworkPatchTest is Test { uint256 baseTokenId3 = _testBaseNFT.mint(_userAddress); TestPatchFragmentNFT testPatchFragmentNFT = new TestPatchFragmentNFT(address(_prot)); _testPatchLiteRefNFT.registerReferenceAddress(address(testPatchFragmentNFT)); - uint256 liteRefId = _prot.createPatch(address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); - uint256 liteRefId2 = _prot.createPatch(address(_testBaseNFT), baseTokenId2, address(_testPatchLiteRefNFT)); - uint256 fragmentTokenId = _prot.createPatch(address(_testBaseNFT), baseTokenId3, address(testPatchFragmentNFT)); + uint256 liteRefId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + uint256 liteRefId2 = _prot.createPatch(_user2Address, address(_testBaseNFT), baseTokenId2, address(_testPatchLiteRefNFT)); + uint256 fragmentTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId3, address(testPatchFragmentNFT)); // cannot assign patch to a literef that this person does not own vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); _prot.assignNFT(address(testPatchFragmentNFT), fragmentTokenId, address(_testPatchLiteRefNFT), liteRefId2); diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index 44948e4..0903d55 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -127,7 +127,7 @@ contract PatchworkProtocolTest is Test { _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); - uint256 tokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 tokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); assertEq(tokenId, 0); } @@ -136,7 +136,7 @@ contract PatchworkProtocolTest is Test { _prot.claimScope(_scopeName); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(_testPatchLiteRefNFT))); - _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); } function testCreatePatchNFTVerified() public { @@ -149,7 +149,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.addWhitelist(_scopeName, address(_testPatchLiteRefNFT)); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); - uint256 tokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 tokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); assertEq(tokenId, 0); vm.stopPrank(); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); @@ -158,7 +158,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.removeWhitelist(_scopeName, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(_testPatchLiteRefNFT))); - tokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId + 1, address(_testPatchLiteRefNFT)); + tokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId + 1, address(_testPatchLiteRefNFT)); } function testUserPermissions() public { @@ -174,12 +174,12 @@ contract PatchworkProtocolTest is Test { vm.stopPrank(); vm.startPrank(_userAddress); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); vm.stopPrank(); vm.prank(_scopeOwner); _prot.setScopeRules(_scopeName, true, false, true); vm.startPrank(_userAddress); - patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); @@ -199,14 +199,14 @@ contract PatchworkProtocolTest is Test { uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, _scopeName)); - _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.AlreadyPatched.selector, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT))); - patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress); assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _userAddress); @@ -305,7 +305,7 @@ contract PatchworkProtocolTest is Test { _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); vm.stopPrank(); vm.startPrank(_userAddress); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress); uint256 user2FragmentTokenId = _testFragmentLiteRefNFT.mint(_user2Address); @@ -355,7 +355,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_user2Address); assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _user2Address); @@ -376,7 +376,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); //Register _testFragmentLiteRefNFT to _testFragmentLiteRefNFT to allow recursion @@ -447,7 +447,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); _testPatchLiteRefNFT.registerReferenceAddress(address(_testMultiFragmentNFT)); _prot.assignNFT(address(_testMultiFragmentNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); @@ -462,7 +462,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); address[] memory fragmentAddresses = new address[](8); uint256[] memory fragments = new uint256[](8); @@ -582,7 +582,7 @@ contract PatchworkProtocolTest is Test { vm.stopPrank(); vm.startPrank(_userAddress); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); address[] memory fragmentAddresses = new address[](8); uint256[] memory fragments = new uint256[](8); uint256[] memory user2Fragments = new uint256[](8); @@ -689,7 +689,7 @@ contract PatchworkProtocolTest is Test { // test with patch uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); vm.prank(_scopeOwner); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); vm.prank(_userAddress); _testBaseNFT.transferFrom(_userAddress, _user2Address, _testBaseNFTTokenId); assertEq(_user2Address, _testPatchLiteRefNFT.ownerOf(patchTokenId)); @@ -706,7 +706,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); //Register _testFragmentLiteRefNFT to _testFragmentLiteRefNFT to allow recursion @@ -771,7 +771,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); //Register _testFragmentLiteRefNFT to _testFragmentLiteRefNFT to allow recursion @@ -888,7 +888,7 @@ contract PatchworkProtocolTest is Test { uint256 frag1 = _testFragmentLiteRefNFT.mint(_userAddress); uint256 frag2 = testFrag2.mint(_userAddress); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); - uint256 patchTokenId = _prot.createPatch(address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); _prot.assignNFT(address(_testFragmentLiteRefNFT), frag1, address(_testPatchLiteRefNFT), patchTokenId); // The second assign succeeding combined with the assertion that they are equal ref values means there is no collision in the scope. _prot.assignNFT(address(testFrag2), frag2, address(_testFragmentLiteRefNFT), frag1); diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index 3964f15..d891217 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -170,15 +170,19 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { // TODO bulk insert for fewer stores } - function mintPatch(address originalNFTOwner, address originalNFTAddress, uint originalNFTTokenId) external returns (uint256 tokenId){ + function mintPatch(address owner, address originalNFTAddress, uint originalNFTTokenId) external returns (uint256 tokenId){ if (msg.sender != _manager) { revert(); } + // require inherited ownership + if (IERC721(originalNFTAddress).ownerOf(originalNFTTokenId) != owner) { + revert IPatchworkProtocol.NotAuthorized(owner); + } // Just for testing tokenId = _nextTokenId; _nextTokenId++; _storePatch(tokenId, originalNFTAddress, originalNFTTokenId); - _safeMint(originalNFTOwner, tokenId); + _safeMint(owner, tokenId); _metadataStorage[tokenId] = new uint256[](3); return tokenId; } From e89c047cdeb20ab77af1fdf17ff580ed34396b0d Mon Sep 17 00:00:00 2001 From: Rob Green Date: Wed, 8 Nov 2023 09:19:50 -0800 Subject: [PATCH 17/63] 1155 Patch (#40) * 1155 Patch WIP * rename to account * Finalizing a few 1155 things * coverage --- src/IPatchwork1155Patch.sol | 25 +++++++ src/IPatchworkProtocol.sol | 33 ++++++++- src/Patchwork1155Patch.sol | 55 +++++++++++++++ src/PatchworkProtocol.sol | 29 +++++++- test/Patchwork1155Patch.t.sol | 120 +++++++++++++++++++++++++++++++++ test/nfts/Test1155PatchNFT.sol | 48 +++++++++++++ test/nfts/TestBase1155.sol | 16 +++++ 7 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 src/IPatchwork1155Patch.sol create mode 100644 src/Patchwork1155Patch.sol create mode 100644 test/Patchwork1155Patch.t.sol create mode 100644 test/nfts/Test1155PatchNFT.sol create mode 100644 test/nfts/TestBase1155.sol diff --git a/src/IPatchwork1155Patch.sol b/src/IPatchwork1155Patch.sol new file mode 100644 index 0000000..b517ef4 --- /dev/null +++ b/src/IPatchwork1155Patch.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/** +@title Patchwork Protocol 1155 Patch Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting Patchwork patch standard +*/ +interface IPatchwork1155Patch { + /** + @notice Get the scope this NFT claims to belong to + @return string the name of the scope + */ + function getScopeName() external returns (string memory); + + /** + @notice Creates a new token for the owner, representing a patch + @param to Address of the owner of the patch token + @param originalNFTAddress Address of the original NFT + @param originalNFTTokenId ID of the original NFT token + @param originalAccount Address of the original 1155 account + @return tokenId ID of the newly minted token + */ + function mintPatch(address to, address originalNFTAddress, uint256 originalNFTTokenId, address originalAccount) external returns (uint256 tokenId); +} \ No newline at end of file diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index 28550fc..01a5394 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -61,12 +61,21 @@ interface IPatchworkProtocol { /** @notice The token at the given address has already been patched - @param addr Address of the token owner + @param addr Address of the original 721 @param tokenId ID of the patched token @param patchAddress Address of the patch applied */ error AlreadyPatched(address addr, uint256 tokenId, address patchAddress); + /** + @notice The ERC1155 path has already been patched + @param addr Address of the 1155 + @param tokenId ID of the patched token + @param account The account patched + @param patchAddress Address of the patch applied + */ + error ERC1155AlreadyPatched(address addr, uint256 tokenId, address account, address patchAddress); + /** @notice The provided input lengths are not compatible or valid @dev for any multi array inputs, they must be the same length @@ -294,6 +303,18 @@ interface IPatchworkProtocol { */ event Patch(address indexed owner, address originalAddress, uint256 originalTokenId, address indexed patchAddress, uint256 indexed patchTokenId); + /** + @notice Emitted when a patch is minted + @param owner The owner of the patch + @param originalAddress The address of the original NFT's contract + @param originalTokenId The tokenId of the original NFT + @param originalAccount The address of the original 1155's account + @param patchAddress The address of the patch's contract + @param patchTokenId The tokenId of the patch + */ + event ERC1155Patch(address indexed owner, address originalAddress, uint256 originalTokenId, address originalAccount, address indexed patchAddress, uint256 indexed patchTokenId); + + /** @notice Emitted when an account patch is minted @param owner The owner of the patch @@ -463,6 +484,16 @@ interface IPatchworkProtocol { */ function createPatch(address owner, address originalNFTAddress, uint originalNFTTokenId, address patchAddress) external returns (uint256 tokenId); + /** + @notice Create a new 1155 patch + @param originalNFTAddress Address of the original NFT + @param originalNFTTokenId Token ID of the original NFT + @param originalAccount Address of the account to patch + @param patchAddress Address of the IPatchworkPatch to mint + @return tokenId Token ID of the newly created patch + */ + function create1155Patch(address to, address originalNFTAddress, uint originalNFTTokenId, address originalAccount, address patchAddress) external returns (uint256 tokenId); + /** @notice Create a new account patch @param owner The owner of the patch diff --git a/src/Patchwork1155Patch.sol b/src/Patchwork1155Patch.sol new file mode 100644 index 0000000..ef3bd87 --- /dev/null +++ b/src/Patchwork1155Patch.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "./PatchworkNFT.sol"; +import "./IPatchwork1155Patch.sol"; + +/** +@title Patchwork1155Patch +@dev Base implementation of IPatchwork1155Patch +@dev It extends the functionalities of PatchworkNFT and implements the IPatchwork1155Patch interface. +*/ +abstract contract Patchwork1155Patch is PatchworkNFT, IPatchwork1155Patch { + + struct PatchCanonical { + address addr; + uint256 tokenId; + address account; + } + + /// @dev Mapping from token ID to the canonical address of the NFT that this patch is applied to. + mapping(uint256 => PatchCanonical) internal _patchedAddresses; + + /** + @dev See {IERC165-supportsInterface} + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(IPatchwork1155Patch).interfaceId || + super.supportsInterface(interfaceID); + } + + /** + @dev See {IPatchworkNFT-getScopeName} + */ + function getScopeName() public view virtual override(PatchworkNFT, IPatchwork1155Patch) returns (string memory) { + return _scopeName; + } + + /** + @notice stores a patch + @param tokenId the tokenId of the patch + @param originalNFTAddress the address of the original ERC-1155 we are patching + @param originalNFTTokenId the tokenId of the original ERC-1155 we are patching + @param account the account of the ERC-1155 we are patching + */ + function _storePatch(uint256 tokenId, address originalNFTAddress, uint256 originalNFTTokenId, address account) internal virtual { + _patchedAddresses[tokenId] = PatchCanonical(originalNFTAddress, originalNFTTokenId, account); + } + + /** + @dev See {ERC721-_burn} + */ + function _burn(uint256 /*tokenId*/) internal virtual override { + revert IPatchworkProtocol.UnsupportedOperation(); + } +} \ No newline at end of file diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index d97a5d4..a7655d1 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -6,6 +6,7 @@ import "./IPatchworkSingleAssignableNFT.sol"; import "./IPatchworkMultiAssignableNFT.sol"; import "./IPatchworkLiteRef.sol"; import "./IPatchworkPatch.sol"; +import "./IPatchwork1155Patch.sol"; import "./IPatchworkAccountPatch.sol"; import "./IPatchworkProtocol.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; @@ -144,7 +145,7 @@ contract PatchworkProtocol is IPatchworkProtocol { } /** - @dev See {IPatchworkProtocol-createAccountPatch} + @dev See {IPatchworkProtocol-createPatch} */ function createPatch(address owner, address originalNFTAddress, uint originalNFTTokenId, address patchAddress) public returns (uint256 tokenId) { IPatchworkPatch patch = IPatchworkPatch(patchAddress); @@ -170,6 +171,32 @@ contract PatchworkProtocol is IPatchworkProtocol { return tokenId; } + /** + @dev See {IPatchworkProtocol-create1155Patch} + */ + function create1155Patch(address to, address originalNFTAddress, uint originalNFTTokenId, address originalAccount, address patchAddress) public returns (uint256 tokenId) { + IPatchwork1155Patch patch = IPatchwork1155Patch(patchAddress); + string memory scopeName = patch.getScopeName(); + // mint a Patch that is soulbound to the originalNFT using the contract address at patchAddress which must support Patchwork metadata + Scope storage scope = _mustHaveScope(scopeName); + _mustBeWhitelisted(scopeName, scope, patchAddress); + if (scope.owner == msg.sender || scope.operators[msg.sender]) { + // continue + } else if (scope.allowUserPatch) { + // continue + } else { + revert NotAuthorized(msg.sender); + } + // limit this to one unique patch (originalNFTAddress+TokenID+patchAddress) + bytes32 _hash = keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId, originalAccount, patchAddress)); + if (scope.uniquePatches[_hash]) { + revert ERC1155AlreadyPatched(originalNFTAddress, originalNFTTokenId, originalAccount, patchAddress); + } + scope.uniquePatches[_hash] = true; + tokenId = patch.mintPatch(to, originalNFTAddress, originalNFTTokenId, originalAccount); + emit ERC1155Patch(to, originalNFTAddress, originalNFTTokenId, originalAccount, patchAddress, tokenId); + return tokenId; + } /** @dev See {IPatchworkProtocol-createAccountPatch} */ diff --git a/test/Patchwork1155Patch.t.sol b/test/Patchwork1155Patch.t.sol new file mode 100644 index 0000000..9190d08 --- /dev/null +++ b/test/Patchwork1155Patch.t.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "../src/PatchworkProtocol.sol"; +import "./nfts/Test1155PatchNFT.sol"; +import "./nfts/TestBase1155.sol"; + +contract Patchwork1155PatchTest is Test { + + PatchworkProtocol _prot; + + string _scopeName; + address _defaultUser; + address _scopeOwner; + address _patchworkOwner; + address _userAddress; + address _user2Address; + + function setUp() public { + _defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; + _patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; + _userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; + _user2Address = address(550001); + _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; + + vm.prank(_patchworkOwner); + _prot = new PatchworkProtocol(); + + vm.startPrank(_scopeOwner); + _scopeName = "testscope"; + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + + vm.stopPrank(); + } + + function testScopeName() public { + vm.prank(_scopeOwner); + Test1155PatchNFT testAccountPatchNFT = new Test1155PatchNFT(address(_prot)); + assertEq(_scopeName, testAccountPatchNFT.getScopeName()); + } + + function testSupportsInterface() public { + vm.prank(_scopeOwner); + Test1155PatchNFT testAccountPatchNFT = new Test1155PatchNFT(address(_prot)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IERC165).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IERC721).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IERC4906).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IERC5192).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchwork1155Patch).interfaceId)); + } + + function test1155Patch() public { + vm.startPrank(_scopeOwner); + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot)); + TestBase1155 base1155 = new TestBase1155(); + uint256 b = base1155.mint(_userAddress, 1, 5); + vm.stopPrank(); + vm.startPrank(address(_prot)); + // basic mints should work + test1155PatchNFT.mintPatch(_userAddress, address(base1155), b, _userAddress); + // global + test1155PatchNFT.mintPatch(_userAddress, address(base1155), b, address(0)); + vm.stopPrank(); + // no auth + vm.expectRevert(); + test1155PatchNFT.mintPatch(_userAddress, address(base1155), b, _userAddress); + // global + vm.expectRevert(); + test1155PatchNFT.mintPatch(_userAddress, address(base1155), b, address(0)); + } + + function test1155PatchProto() public { + vm.startPrank(_scopeOwner); + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot)); + TestBase1155 base1155 = new TestBase1155(); + uint256 b = base1155.mint(_userAddress, 1, 5); + + // Account patch + _prot.create1155Patch(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); + + // Account patch can't have duplicate + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ERC1155AlreadyPatched.selector, address(base1155), b, _userAddress, address(test1155PatchNFT))); + _prot.create1155Patch(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); + // Global patch + _prot.create1155Patch(_scopeOwner, address(base1155), b, address(0), address(test1155PatchNFT)); + // Global patch can't have duplicate + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ERC1155AlreadyPatched.selector, address(base1155), b, address(0), address(test1155PatchNFT))); + _prot.create1155Patch(_scopeOwner, address(base1155), b, address(0), address(test1155PatchNFT)); + // no user patching allowed + vm.stopPrank(); + vm.startPrank(_userAddress); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + _prot.create1155Patch(_scopeOwner, address(base1155), b, address(1), address(test1155PatchNFT)); + } + + function test1155PatchUserPatch() public { + vm.prank(_scopeOwner); + _prot.setScopeRules(_scopeName, true, false, false); + // Not same owner model, yes transferrable + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot)); + TestBase1155 base1155 = new TestBase1155(); + uint256 b = base1155.mint(_userAddress, 1, 5); + // user can mint + _prot.create1155Patch(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); + } + + function testBurn() public { + vm.startPrank(_scopeOwner); + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot)); + TestBase1155 base1155 = new TestBase1155(); + uint256 b = base1155.mint(_userAddress, 1, 5); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedOperation.selector)); + test1155PatchNFT.burn(b); + } +} \ No newline at end of file diff --git a/test/nfts/Test1155PatchNFT.sol b/test/nfts/Test1155PatchNFT.sol new file mode 100644 index 0000000..107e545 --- /dev/null +++ b/test/nfts/Test1155PatchNFT.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "../../src/Patchwork1155Patch.sol"; + +contract Test1155PatchNFT is Patchwork1155Patch { + + uint256 _nextTokenId = 0; + + struct Test1155PatchNFTMetadata { + uint256 thing; + } + + constructor(address manager_) PatchworkNFT("testscope", "Test1155PatchNFT", "TPLR", msg.sender, manager_) { + } + + function schemaURI() pure external returns (string memory) { + return "https://mything/my-nft-metadata.json"; + } + + function imageURI(uint256 _tokenId) pure external returns (string memory) { + return string(abi.encodePacked("https://mything/nft-", _tokenId)); + } + + function schema() pure external returns (MetadataSchema memory) { + MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](1); + entries[0] = MetadataSchemaEntry(1, 0, FieldType.UINT256, 0, FieldVisibility.PUBLIC, 2, 0, "thing"); + return MetadataSchema(1, entries); + } + + function mintPatch(address to, address originalNFTAddress, uint originalNFTTokenId, address account) external returns (uint256 tokenId){ + if (msg.sender != _manager) { + revert(); + } + // Just for testing + tokenId = _nextTokenId; + _nextTokenId++; + _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, account); + _safeMint(to, tokenId); + _metadataStorage[tokenId] = new uint256[](1); + return tokenId; + } + + function burn(uint256 tokenId) external { + _burn(tokenId); + } + +} \ No newline at end of file diff --git a/test/nfts/TestBase1155.sol b/test/nfts/TestBase1155.sol new file mode 100644 index 0000000..97b791a --- /dev/null +++ b/test/nfts/TestBase1155.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + + +contract TestBase1155 is ERC1155 { + + constructor() ERC1155("http://myurl/") { + } + + function mint(address to, uint256 tokenId, uint256 amount) public returns (uint256) { + _mint(to, tokenId, amount, ""); + return tokenId; + } +} \ No newline at end of file From ae611b2632ecb87e5e5d11b2a0da5808b7a68769 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Mon, 13 Nov 2023 09:08:45 -0800 Subject: [PATCH 18/63] Dynamic arrays (#41) * Changed metadata type for array length * WIP * Dynamic array basic operations in * More dynamic array work * Batch add reference implementation * mostly working * Oops, fixed copy paste * let's not bother with that right now --- src/IPatchworkLiteRef.sol | 24 +- src/IPatchworkNFT.sol | 2 +- src/PatchworkLiteRef.sol | 15 + src/PatchworkProtocol.sol | 6 +- test/TestDynamicArrayLiteRefNFT.t.sol | 112 ++++++++ test/nfts/TestAccountPatchNFT.sol | 2 +- test/nfts/TestDynamicArrayLiteRefNFT.sol | 344 +++++++++++++++++++++++ test/nfts/TestFragmentLiteRefNFT.sol | 12 +- test/nfts/TestMultiFragmentNFT.sol | 2 +- test/nfts/TestPatchFragmentNFT.sol | 14 +- test/nfts/TestPatchLiteRefNFT.sol | 20 +- test/nfts/TestPatchworkNFT.sol | 2 +- 12 files changed, 522 insertions(+), 33 deletions(-) create mode 100644 test/TestDynamicArrayLiteRefNFT.t.sol create mode 100644 test/nfts/TestDynamicArrayLiteRefNFT.sol diff --git a/src/IPatchworkLiteRef.sol b/src/IPatchworkLiteRef.sol index 040f954..d1737ba 100644 --- a/src/IPatchworkLiteRef.sol +++ b/src/IPatchworkLiteRef.sol @@ -104,17 +104,35 @@ interface IPatchworkLiteRef { /** @notice Loads a reference address and token ID at a given index + @param ourTokenId ID of the token @param idx Index to load from @return addr Address @return tokenId Token ID */ - function loadReferenceAddressAndTokenId(uint256 idx) external view returns (address addr, uint256 tokenId); + function loadReferenceAddressAndTokenId(uint256 ourTokenId, uint256 idx) external view returns (address addr, uint256 tokenId); /** - @notice Loads all references for a given token ID + @notice Loads all static references for a given token ID @param tokenId ID of the token @return addresses Array of addresses @return tokenIds Array of token IDs */ - function loadAllReferences(uint256 tokenId) external view returns (address[] memory addresses, uint256[] memory tokenIds); + function loadAllStaticReferences(uint256 tokenId) external view returns (address[] memory addresses, uint256[] memory tokenIds); + + /** + @notice Count all dynamic references for a given token ID + @param tokenId ID of the token + @return count the number of dynamic references + */ + function getDynamicReferenceCount(uint256 tokenId) external view returns (uint256 count); + + /** + @notice Load a page of dynamic references for a given token ID + @param tokenId ID of the token + @param offset The starting offset 0-indexed + @param count The maximum number of references to return + @return addresses An array of reference addresses + @return tokenIds An array of reference token IDs + */ + function loadDynamicReferencePage(uint256 tokenId, uint256 offset, uint256 count) external view returns (address[] memory addresses, uint256[] memory tokenIds); } diff --git a/src/IPatchworkNFT.sol b/src/IPatchworkNFT.sol index 2e6416f..0c9567c 100644 --- a/src/IPatchworkNFT.sol +++ b/src/IPatchworkNFT.sol @@ -52,7 +52,7 @@ interface PatchworkNFTInterfaceMeta { uint256 id; ///< Index or unique identifier of the entry. uint256 permissionId; ///< Permission identifier associated with the entry. FieldType fieldType; ///< Type of field data (from the FieldType enum). - uint256 arrayLength; ///< Length of array for the field (0 means it's a single field). + uint256 fieldCount; ///< Number of elements of this field (0 = Dynamic Array, 1 = Single, >1 = Static Array) FieldVisibility visibility; ///< Visibility level of the field. uint256 slot; ///< Starting storage slot, may span multiple slots based on width. uint256 offset; ///< Offset in bits within the storage slot. diff --git a/src/PatchworkLiteRef.sol b/src/PatchworkLiteRef.sol index 378aa85..592b736 100644 --- a/src/PatchworkLiteRef.sol +++ b/src/PatchworkLiteRef.sol @@ -117,6 +117,21 @@ abstract contract PatchworkLiteRef is IPatchworkLiteRef, ERC165 { return (_referenceAddresses[refId], tokenId); } + /** + @dev See {IPatchworkLiteRef-loadAllStaticReferences} + */ + function loadAllStaticReferences(uint256 tokenId) public virtual view returns (address[] memory addresses, uint256[] memory tokenIds) {} + + /** + @dev See {IPatchworkLiteRef-getDynamicReferenceCount} + */ + function getDynamicReferenceCount(uint256 tokenId) public virtual view returns (uint256 count) {} + + /** + @dev See {IPatchworkLiteRef-loadDynamicReferencePage} + */ + function loadDynamicReferencePage(uint256 tokenId, uint256 offset, uint256 count) public virtual view returns (address[] memory addresses, uint256[] memory tokenIds) {} + modifier _mustHaveWriteAuth { if (!_checkWriteAuth()) { revert IPatchworkProtocol.NotAuthorized(msg.sender); diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index a7655d1..57cfbc6 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -416,7 +416,7 @@ contract PatchworkProtocol is IPatchworkProtocol { } if (IERC165(nft).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { IPatchworkLiteRef liteRefNFT = IPatchworkLiteRef(nft); - (address[] memory addresses, uint256[] memory tokenIds) = liteRefNFT.loadAllReferences(tokenId); + (address[] memory addresses, uint256[] memory tokenIds) = liteRefNFT.loadAllStaticReferences(tokenId); for (uint i = 0; i < addresses.length; i++) { if (addresses[i] != address(0)) { _applyAssignedTransfer(addresses[i], from, to, tokenIds[i], nft, tokenId); @@ -438,7 +438,7 @@ contract PatchworkProtocol is IPatchworkProtocol { if (IERC165(nft).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { address nft_ = nft; // local variable prevents optimizer stack issue in v0.8.18 IPatchworkLiteRef liteRefNFT = IPatchworkLiteRef(nft); - (address[] memory addresses, uint256[] memory tokenIds) = liteRefNFT.loadAllReferences(tokenId); + (address[] memory addresses, uint256[] memory tokenIds) = liteRefNFT.loadAllStaticReferences(tokenId); for (uint i = 0; i < addresses.length; i++) { if (addresses[i] != address(0)) { _applyAssignedTransfer(addresses[i], from, to, tokenIds[i], nft_, tokenId); @@ -453,7 +453,7 @@ contract PatchworkProtocol is IPatchworkProtocol { function updateOwnershipTree(address nft, uint256 tokenId) public { if (IERC165(nft).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { IPatchworkLiteRef liteRefNFT = IPatchworkLiteRef(nft); - (address[] memory addresses, uint256[] memory tokenIds) = liteRefNFT.loadAllReferences(tokenId); + (address[] memory addresses, uint256[] memory tokenIds) = liteRefNFT.loadAllStaticReferences(tokenId); for (uint i = 0; i < addresses.length; i++) { if (addresses[i] != address(0)) { updateOwnershipTree(addresses[i], tokenIds[i]); diff --git a/test/TestDynamicArrayLiteRefNFT.t.sol b/test/TestDynamicArrayLiteRefNFT.t.sol new file mode 100644 index 0000000..1f97f90 --- /dev/null +++ b/test/TestDynamicArrayLiteRefNFT.t.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "../src/PatchworkProtocol.sol"; +import "./nfts/TestDynamicArrayLiteRefNFT.sol"; + +contract PatchworkAccountPatchTest is Test { + + PatchworkProtocol _prot; + + string _scopeName; + address _defaultUser; + address _scopeOwner; + address _patchworkOwner; + address _userAddress; + address _user2Address; + + function setUp() public { + _defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; + _patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; + _userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; + _user2Address = address(550001); + _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; + + vm.prank(_patchworkOwner); + _prot = new PatchworkProtocol(); + + vm.startPrank(_scopeOwner); + _scopeName = "testscope"; + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + + vm.stopPrank(); + } + + function testDynamics() public { + TestDynamicArrayLiteRefNFT nft = new TestDynamicArrayLiteRefNFT(address(_prot)); + nft.registerReferenceAddress(address(0x55)); + uint256 m = nft.mint(_userAddress); + assertEq(0, nft.getDynamicReferenceCount(m)); + for (uint256 i = 0; i < 9; i++) { + (uint64 ref,) = nft.getLiteReference(address(0x55), i); + nft.addReference(m, ref); + assertEq(i+1, nft.getDynamicReferenceCount(m)); + } + + (, uint256[] memory tokenIds) = nft.loadDynamicReferencePage(m, 0, 3); + assertEq(tokenIds[0], 0); + assertEq(tokenIds[1], 1); + assertEq(tokenIds[2], 2); + (, tokenIds) = nft.loadDynamicReferencePage(m, 3, 3); + assertEq(tokenIds[0], 3); + assertEq(tokenIds[1], 4); + assertEq(tokenIds[2], 5); + (, tokenIds) = nft.loadDynamicReferencePage(m, 6, 4); + assertEq(tokenIds[0], 6); + assertEq(tokenIds[1], 7); + assertEq(tokenIds[2], 8); + (, tokenIds) = nft.loadDynamicReferencePage(m, 9, 4); + assertEq(tokenIds.length, 0); + (, tokenIds) = nft.loadDynamicReferencePage(m, 10, 4); + assertEq(tokenIds.length, 0); + for (uint256 i = 0; i < 9; i++) { + (uint64 ref,) = nft.getLiteReference(address(0x55), i); + nft.removeReference(m, ref); + assertEq(8-i, nft.getDynamicReferenceCount(m)); + } + } + + function testBatchAdd() public { + TestDynamicArrayLiteRefNFT nft = new TestDynamicArrayLiteRefNFT(address(_prot)); + nft.registerReferenceAddress(address(0x55)); + uint256 m = nft.mint(_userAddress); + assertEq(0, nft.getDynamicReferenceCount(m)); + uint64[] memory refs = new uint64[](11); + for (uint256 i = 0; i < 11; i++) { + (uint64 ref,) = nft.getLiteReference(address(0x55), i); + refs[i] = ref; + } + nft.batchAddReferences(m, refs); + assertEq(11, nft.getDynamicReferenceCount(m)); + + (, uint256[] memory tokenIds) = nft.loadDynamicReferencePage(m, 0, 3); + assertEq(tokenIds[0], 0); + assertEq(tokenIds[1], 1); + assertEq(tokenIds[2], 2); + (, tokenIds) = nft.loadDynamicReferencePage(m, 3, 3); + assertEq(tokenIds[0], 3); + assertEq(tokenIds[1], 4); + assertEq(tokenIds[2], 5); + (, tokenIds) = nft.loadDynamicReferencePage(m, 6, 5); + assertEq(tokenIds[0], 6); + assertEq(tokenIds[1], 7); + assertEq(tokenIds[2], 8); + assertEq(tokenIds[3], 9); + assertEq(tokenIds[4], 10); + (, tokenIds) = nft.loadDynamicReferencePage(m, 11, 4); + assertEq(tokenIds.length, 0); + (, tokenIds) = nft.loadDynamicReferencePage(m, 13, 4); + assertEq(tokenIds.length, 0); + for (uint256 i = 0; i < 11; i++) { + (uint64 ref,) = nft.getLiteReference(address(0x55), i); + nft.removeReference(m, ref); + assertEq(10-i, nft.getDynamicReferenceCount(m)); + } + } + + +} \ No newline at end of file diff --git a/test/nfts/TestAccountPatchNFT.sol b/test/nfts/TestAccountPatchNFT.sol index e225ae5..8af06d3 100644 --- a/test/nfts/TestAccountPatchNFT.sol +++ b/test/nfts/TestAccountPatchNFT.sol @@ -26,7 +26,7 @@ contract TestAccountPatchNFT is PatchworkAccountPatch { function schema() pure external returns (MetadataSchema memory) { MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](1); - entries[0] = MetadataSchemaEntry(1, 0, FieldType.UINT256, 0, FieldVisibility.PUBLIC, 2, 0, "thing"); + entries[0] = MetadataSchemaEntry(1, 0, FieldType.UINT256, 1, FieldVisibility.PUBLIC, 2, 0, "thing"); return MetadataSchema(1, entries); } diff --git a/test/nfts/TestDynamicArrayLiteRefNFT.sol b/test/nfts/TestDynamicArrayLiteRefNFT.sol new file mode 100644 index 0000000..ebdbc9e --- /dev/null +++ b/test/nfts/TestDynamicArrayLiteRefNFT.sol @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +/* + Prototype - Generated Patchwork Meta contract for Totem NFT. + + Is attached to any normal NFT and is scoped for a specific application. + + Has metadata as defined in totem-metadata.json +*/ + +import "../../src/PatchworkNFT.sol"; +import "../../src/PatchworkLiteRef.sol"; +import "forge-std/console.sol"; + +struct TestDynamicArrayLiteRefNFTMetadata { + uint16 xp; + uint8 level; + uint16 xpLost; + uint16 stakedMade; + uint16 stakedCorrect; + uint8 evolution; + string nickname; +} + +struct DynamicLiteRefs { + uint256[] slots; // 4 per + mapping(uint64 => uint256) idx; +} + +contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef { + + uint256 _nextTokenId; + + mapping(uint256 => DynamicLiteRefs) internal _dynamicLiterefStorage; // tokenId => indexed slots + + constructor(address manager_) PatchworkNFT("testscope", "TestPatchLiteRef", "TPLR", msg.sender, manager_) PatchworkLiteRef() { + } + + // ERC-165 + function supportsInterface(bytes4 interfaceID) public view virtual override(PatchworkNFT, PatchworkLiteRef) returns (bool) { + return PatchworkNFT.supportsInterface(interfaceID) || + PatchworkLiteRef.supportsInterface(interfaceID); + } + + function schemaURI() pure external override returns (string memory) { + return "https://mything/my-metadata.json"; + } + + function imageURI(uint256 _tokenId) pure external override returns (string memory) {} + + function setManager(address manager_) external { + require(_checkWriteAuth()); + _manager = manager_; + } + + function mint(address to) external returns (uint256 tokenId) { + tokenId = _nextTokenId; + _nextTokenId++; + _safeMint(to, tokenId); + _metadataStorage[tokenId] = new uint256[](1); + _dynamicLiterefStorage[tokenId].slots = new uint256[](0); + } + + /* + Hard coded prototype schema is: + slot 0 offset 0 = artifactIDs (spans 2) - also we need special built-in handling for < 256 bit IDs + slot 2 offset 0 = xp + slot 2 offset 16 = level + slot 2 offset 24 = xpLost + slot 2 offset 40 = stakedMade + slot 2 offset 56 = stakedCorrect + slot 2 offset 72 = evolution + slot 2 offset 80 = nickname + */ + function schema() pure external override returns (MetadataSchema memory) { + MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](8); + entries[0] = MetadataSchemaEntry(0, 0, FieldType.UINT64, 0, FieldVisibility.PUBLIC, 0, 0, "artifactIDs"); // Dynamic + entries[1] = MetadataSchemaEntry(1, 1, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 0, "xp"); + entries[2] = MetadataSchemaEntry(2, 2, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 2, 16, "level"); + entries[3] = MetadataSchemaEntry(3, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 24, "xpLost"); + entries[4] = MetadataSchemaEntry(4, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 40, "stakedMade"); + entries[5] = MetadataSchemaEntry(5, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 56, "stakedCorrect"); + entries[6] = MetadataSchemaEntry(6, 0, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 2, 72, "evolution"); + entries[7] = MetadataSchemaEntry(7, 0, FieldType.CHAR16, 1, FieldVisibility.PUBLIC, 2, 80, "nickname"); + return MetadataSchema(1, entries); + } + + function packMetadata(TestDynamicArrayLiteRefNFTMetadata memory data) public pure returns (uint256[] memory slots) { + bytes32 nickname; + bytes memory ns = bytes(data.nickname); + + assembly { + nickname := mload(add(ns, 32)) + } + slots = new uint256[](1); + slots[0] = uint256(data.xp) | uint256(data.level) << 16 | uint256(data.xpLost) << 24 | uint256(data.stakedMade) << 40 | uint256(data.stakedCorrect) << 56 | uint256(data.evolution) << 72 | uint256(nickname) >> 128 << 80; + return slots; + } + + function storeMetadata(uint256 _tokenId, TestDynamicArrayLiteRefNFTMetadata memory data) public { + require(_checkTokenWriteAuth(_tokenId), "not authorized"); + _metadataStorage[_tokenId] = packMetadata(data); + } + + function unpackMetadata(uint256[] memory slots) public pure returns (TestDynamicArrayLiteRefNFTMetadata memory data) { + data.xp = uint16(slots[0]); + data.level = uint8(slots[0] >> 16); + data.xpLost = uint16(slots[0] >> 24); + data.stakedMade = uint16(slots[0] >> 40); + data.stakedCorrect = uint16(slots[0] >> 56); + data.evolution = uint8(slots[0] >> 72); + data.nickname = string(abi.encodePacked(bytes16(uint128(slots[0] >> 80)))); + return data; + } + + function loadMetadata(uint256 _tokenId) public view returns (TestDynamicArrayLiteRefNFTMetadata memory data) { + return unpackMetadata(_metadataStorage[_tokenId]); + } + + // Store Only XP + function storeXP(uint256 _tokenId, uint16 xp) public { + require(_checkTokenWriteAuth(_tokenId) || _permissionsAllow[msg.sender] & 0x1 == 1, "not authorized"); + // Slot 2 offset 0: 16 bit value + uint256 cleared = uint256(_metadataStorage[_tokenId][0]) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000; + _metadataStorage[_tokenId][0] = cleared | uint256(xp); + } + + // Load Only XP + function loadXP(uint256 _tokenId) public view returns (uint16) { + return uint16(uint256(_metadataStorage[_tokenId][0])); + } + + // Store Only level + function storeLevel(uint256 _tokenId, uint8 level) public { + require(_checkTokenWriteAuth(_tokenId) || _permissionsAllow[msg.sender] & 0x2 == 2, "not authorized"); + // Slot 2 offset 16: 16 bit value + uint256 cleared = uint256(_metadataStorage[_tokenId][0]) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFF; + _metadataStorage[_tokenId][0] = cleared | (uint256(level) << 16); + } + + // Load Only level + function loadLevel(uint256 _tokenId) public view returns (uint16) { + return uint16(uint256(_metadataStorage[_tokenId][2]) >> 16); + } + + function addReference(uint256 ourTokenId, uint64 referenceAddress) public override { + require(_checkTokenWriteAuth(ourTokenId), "not authorized"); + // to append: find last slot, if it's not full, add, otherwise start a new slot. + DynamicLiteRefs storage store = _dynamicLiterefStorage[ourTokenId]; + uint256 slotsLen = store.slots.length; + if (slotsLen == 0) { + store.slots.push(uint256(referenceAddress)); + store.idx[referenceAddress] = 0; + } else { + uint256 slot = store.slots[slotsLen-1]; + if (slot >= (1 << 192)) { + // new slot (pos 1) + store.slots.push(uint256(referenceAddress)); + store.idx[referenceAddress] = slotsLen; + } else { + store.idx[referenceAddress] = slotsLen-1; + // Reverse search for the next empty subslot + if (slot >= (1 << 128)) { + // pos 4 + store.slots[slotsLen-1] = slot | uint256(referenceAddress) << 192; + } else if (slot >= (1 << 64)) { + // pos 3 + store.slots[slotsLen-1] = slot | uint256(referenceAddress) << 128; + } else { + // pos 2 + store.slots[slotsLen-1] = slot | uint256(referenceAddress) << 64; + } + } + } + } + + function batchAddReferences(uint256 ourTokenId, uint64[] calldata _referenceAddresses) public override { + require(_checkTokenWriteAuth(ourTokenId), "not authorized"); + // do in batches of 4 with 1 remainder pass + DynamicLiteRefs storage store = _dynamicLiterefStorage[ourTokenId]; + uint256 slotsLen = store.slots.length; + if (slotsLen > 0) { + revert("already loaded"); + } + uint256 fullBatchCount = _referenceAddresses.length / 4; + uint256 remainder = _referenceAddresses.length % 4; + for (uint256 batch = 0; batch < fullBatchCount; batch++) { + uint256 refIdx = batch * 4; + uint256 slot = uint256(_referenceAddresses[refIdx]) | (uint256(_referenceAddresses[refIdx+1]) << 64) | (uint256(_referenceAddresses[refIdx+2]) << 128) | (uint256(_referenceAddresses[refIdx+3]) << 192); + store.slots.push(slot); + store.idx[_referenceAddresses[refIdx]] = batch; + store.idx[_referenceAddresses[refIdx + 1]] = batch; + store.idx[_referenceAddresses[refIdx + 2]] = batch; + store.idx[_referenceAddresses[refIdx + 3]] = batch; + } + uint256 rSlot; + for (uint256 i = 0; i < remainder; i++) { + uint256 idx = (fullBatchCount * 4) + i; + rSlot = rSlot | (uint256(_referenceAddresses[idx]) << (i * 64)); + store.idx[_referenceAddresses[idx]] = fullBatchCount; + } + store.slots.push(rSlot); + } + + function removeReference(uint256 ourTokenId, uint64 referenceAddress) public override { + require(_checkTokenWriteAuth(ourTokenId), "not authorized"); + DynamicLiteRefs storage store = _dynamicLiterefStorage[ourTokenId]; + uint256 slotsLen = store.slots.length; + if (slotsLen == 0) { + revert("not found"); + } + + console.log("removing"); + console.logBytes8(bytes8(referenceAddress)); + for (uint256 i = 0; i < store.slots.length; i++) { + console.logBytes32(bytes32(store.slots[i])); + } + uint256 count = getDynamicReferenceCount(ourTokenId); + if (count == 1) { + if (store.slots[0] == referenceAddress) { + store.slots.pop(); + delete store.idx[referenceAddress]; + } else { + revert("not found"); + } + } else { + // remember and remove the last ref + uint256 lastIdx = slotsLen-1; + uint256 slot = store.slots[lastIdx]; + uint64 lastRef; + if (slot >= (1 << 192)) { + // pos 4 + lastRef = uint64(slot >> 192); + store.slots[lastIdx] = slot & 0x0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + } else if (slot >= (1 << 128)) { + // pos 3 + lastRef = uint64(slot >> 128); + store.slots[lastIdx] = slot & 0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + } else if (slot >= (1 << 64)) { + // pos 2 + lastRef = uint64(slot >> 64); + store.slots[lastIdx] = slot & 0x000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFF; + } else { + // pos 1 + lastRef = uint64(slot); + store.slots.pop(); + } + if (lastRef == referenceAddress) { + // it was the last ref. No need to replace anything. It's already cleared so just clear the index + delete store.idx[referenceAddress]; + } else { + // Find the ref and replace it with lastRef then update indexes + uint256 refSlotIdx = store.idx[referenceAddress]; + slot = store.slots[refSlotIdx]; + if (uint64(slot >> 192) == referenceAddress) { + slot = slot & 0x0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + slot = slot | (uint256(lastRef) << 192); + } else if (uint64(slot >> 128) == referenceAddress) { + slot = slot & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + slot = slot | (uint256(lastRef) << 128); + } else if (uint64(slot >> 64) == referenceAddress) { + slot = slot & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF; + slot = slot | (uint256(lastRef) << 64); + } else if (uint64(slot) == referenceAddress) { + slot = slot & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000; + slot = slot | uint256(lastRef); + } else { + revert("storage integrity error"); + } + store.slots[refSlotIdx] = slot; + store.idx[lastRef] = refSlotIdx; + delete store.idx[referenceAddress]; + } + } + } + + function loadReferenceAddressAndTokenId(uint256 ourTokenId, uint256 idx) public view returns (address addr, uint256 tokenId) { + uint256[] storage slots = _dynamicLiterefStorage[ourTokenId].slots; + uint slotNumber = idx / 4; // integer division will get the correct slot number + uint shift = (idx % 4) * 64; // the remainder will give the correct shift + uint64 ref = uint64(slots[slotNumber] >> shift); + (addr, tokenId) = getReferenceAddressAndTokenId(ref); + } + + function getDynamicReferenceCount(uint256 tokenId) public view override returns (uint256 count) { + DynamicLiteRefs storage store = _dynamicLiterefStorage[tokenId]; + uint256 slotsLen = store.slots.length; + if (slotsLen == 0) { + return 0; + } else { + uint256 slot = store.slots[slotsLen-1]; + // You could get rid of this conditional stuff if you had a log function + if (slot >= (1 << 192)) { + return slotsLen * 4; + } else { + // Reverse search for the next empty subslot + if (slot >= (1 << 128)) { + // pos 4 + return (slotsLen-1) * 4 + 3; + } else if (slot >= (1 << 64)) { + // pos 3 + return (slotsLen-1) * 4 + 2; + } else { + // pos 2 + return (slotsLen-1) * 4 + 1; + } + } + } + } + + function loadDynamicReferencePage(uint256 tokenId, uint256 offset, uint256 count) public view override returns (address[] memory addresses, uint256[] memory tokenIds) { + uint256 refCount = getDynamicReferenceCount(tokenId); + if (offset >= refCount) { + return (new address[](0), new uint256[](0)); + } + uint256 realCount = refCount - offset; + if (realCount > count) { + realCount = count; + } + addresses = new address[](realCount); + tokenIds = new uint256[](realCount); + uint256[] storage slots = _dynamicLiterefStorage[tokenId].slots; + // start at offset + for (uint256 i = 0; i < realCount; i++) { + uint256 idx = offset + i; + uint slotNumber = idx / 4; // integer division will get the correct slot number + uint shift = (idx % 4) * 64; // the remainder will give the correct shift + uint64 ref = uint64(slots[slotNumber] >> shift); + (address attributeAddress, uint256 attributeTokenId) = getReferenceAddressAndTokenId(ref); + addresses[i] = attributeAddress; + tokenIds[i] = attributeTokenId; + } + } + + function _checkWriteAuth() internal override(PatchworkNFT, PatchworkLiteRef) view returns (bool allow) { + return PatchworkNFT._checkWriteAuth(); + } + + function burn(uint256 tokenId) public { + // test only + _burn(tokenId); + } +} \ No newline at end of file diff --git a/test/nfts/TestFragmentLiteRefNFT.sol b/test/nfts/TestFragmentLiteRefNFT.sol index 2c19712..151ca35 100644 --- a/test/nfts/TestFragmentLiteRefNFT.sol +++ b/test/nfts/TestFragmentLiteRefNFT.sol @@ -69,9 +69,9 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef { function schema() pure external returns (MetadataSchema memory) { MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](8); entries[0] = MetadataSchemaEntry(0, 0, FieldType.UINT64, 8, FieldVisibility.PUBLIC, 0, 0, "artifactIDs"); - entries[1] = MetadataSchemaEntry(1, 0, FieldType.UINT8, 0, FieldVisibility.PUBLIC, 2, 0, "fragmentType"); - entries[2] = MetadataSchemaEntry(2, 0, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 8, "rarity"); - entries[3] = MetadataSchemaEntry(3, 0, FieldType.CHAR16, 0, FieldVisibility.PUBLIC, 2, 16, "name"); + entries[1] = MetadataSchemaEntry(1, 0, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 2, 0, "fragmentType"); + entries[2] = MetadataSchemaEntry(2, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 8, "rarity"); + entries[3] = MetadataSchemaEntry(3, 0, FieldType.CHAR16, 1, FieldVisibility.PUBLIC, 2, 16, "name"); return MetadataSchema(1, entries); } @@ -170,15 +170,15 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef { } } - function loadReferenceAddressAndTokenId(uint256 idx) public view returns (address addr, uint256 tokenId) { - uint256[] storage slots = _metadataStorage[tokenId]; + function loadReferenceAddressAndTokenId(uint256 ourTokenId, uint256 idx) public view returns (address addr, uint256 tokenId) { + uint256[] storage slots = _metadataStorage[ourTokenId]; uint slotNumber = idx / 4; uint shift = (idx % 4) * 64; uint64 attributeId = uint64(slots[slotNumber] >> shift); return getReferenceAddressAndTokenId(attributeId); } - function loadAllReferences(uint256 tokenId) public view returns (address[] memory addresses, uint256[] memory tokenIds) { + function loadAllStaticReferences(uint256 tokenId) public view override returns (address[] memory addresses, uint256[] memory tokenIds) { uint256[] storage slots = _metadataStorage[tokenId]; addresses = new address[](8); tokenIds = new uint256[](8); diff --git a/test/nfts/TestMultiFragmentNFT.sol b/test/nfts/TestMultiFragmentNFT.sol index dbc79de..cb95bb5 100644 --- a/test/nfts/TestMultiFragmentNFT.sol +++ b/test/nfts/TestMultiFragmentNFT.sol @@ -40,7 +40,7 @@ contract TestMultiFragmentNFT is PatchworkFragmentMulti { */ function schema() pure external returns (MetadataSchema memory) { MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](8); - entries[0] = MetadataSchemaEntry(0, 0, FieldType.UINT8, 0, FieldVisibility.PUBLIC, 0, 0, "nothing"); + entries[0] = MetadataSchemaEntry(0, 0, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 0, 0, "nothing"); return MetadataSchema(1, entries); } diff --git a/test/nfts/TestPatchFragmentNFT.sol b/test/nfts/TestPatchFragmentNFT.sol index ca80d60..ecefd92 100644 --- a/test/nfts/TestPatchFragmentNFT.sol +++ b/test/nfts/TestPatchFragmentNFT.sol @@ -79,13 +79,13 @@ contract TestPatchFragmentNFT is PatchworkPatch, PatchworkFragmentSingle { */ function schema() pure external override returns (MetadataSchema memory) { MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](8); - entries[1] = MetadataSchemaEntry(1, 1, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 0, "xp"); - entries[2] = MetadataSchemaEntry(2, 2, FieldType.UINT8, 0, FieldVisibility.PUBLIC, 2, 16, "level"); - entries[3] = MetadataSchemaEntry(3, 0, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 24, "xpLost"); - entries[4] = MetadataSchemaEntry(4, 0, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 40, "stakedMade"); - entries[5] = MetadataSchemaEntry(5, 0, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 56, "stakedCorrect"); - entries[6] = MetadataSchemaEntry(6, 0, FieldType.UINT8, 0, FieldVisibility.PUBLIC, 2, 72, "evolution"); - entries[7] = MetadataSchemaEntry(7, 0, FieldType.CHAR16, 0, FieldVisibility.PUBLIC, 2, 80, "nickname"); + entries[1] = MetadataSchemaEntry(1, 1, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 0, "xp"); + entries[2] = MetadataSchemaEntry(2, 2, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 2, 16, "level"); + entries[3] = MetadataSchemaEntry(3, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 24, "xpLost"); + entries[4] = MetadataSchemaEntry(4, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 40, "stakedMade"); + entries[5] = MetadataSchemaEntry(5, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 56, "stakedCorrect"); + entries[6] = MetadataSchemaEntry(6, 0, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 2, 72, "evolution"); + entries[7] = MetadataSchemaEntry(7, 0, FieldType.CHAR16, 1, FieldVisibility.PUBLIC, 2, 80, "nickname"); return MetadataSchema(1, entries); } diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index d891217..43bb396 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -61,13 +61,13 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { function schema() pure external override returns (MetadataSchema memory) { MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](8); entries[0] = MetadataSchemaEntry(0, 0, FieldType.UINT64, 8, FieldVisibility.PUBLIC, 0, 0, "artifactIDs"); - entries[1] = MetadataSchemaEntry(1, 1, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 0, "xp"); - entries[2] = MetadataSchemaEntry(2, 2, FieldType.UINT8, 0, FieldVisibility.PUBLIC, 2, 16, "level"); - entries[3] = MetadataSchemaEntry(3, 0, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 24, "xpLost"); - entries[4] = MetadataSchemaEntry(4, 0, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 40, "stakedMade"); - entries[5] = MetadataSchemaEntry(5, 0, FieldType.UINT16, 0, FieldVisibility.PUBLIC, 2, 56, "stakedCorrect"); - entries[6] = MetadataSchemaEntry(6, 0, FieldType.UINT8, 0, FieldVisibility.PUBLIC, 2, 72, "evolution"); - entries[7] = MetadataSchemaEntry(7, 0, FieldType.CHAR16, 0, FieldVisibility.PUBLIC, 2, 80, "nickname"); + entries[1] = MetadataSchemaEntry(1, 1, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 0, "xp"); + entries[2] = MetadataSchemaEntry(2, 2, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 2, 16, "level"); + entries[3] = MetadataSchemaEntry(3, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 24, "xpLost"); + entries[4] = MetadataSchemaEntry(4, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 40, "stakedMade"); + entries[5] = MetadataSchemaEntry(5, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 56, "stakedCorrect"); + entries[6] = MetadataSchemaEntry(6, 0, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 2, 72, "evolution"); + entries[7] = MetadataSchemaEntry(7, 0, FieldType.CHAR16, 1, FieldVisibility.PUBLIC, 2, 80, "nickname"); return MetadataSchema(1, entries); } @@ -213,15 +213,15 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { } } - function loadReferenceAddressAndTokenId(uint256 idx) public view returns (address addr, uint256 tokenId) { - uint256[] storage slots = _metadataStorage[tokenId]; + function loadReferenceAddressAndTokenId(uint256 ourTokenId, uint256 idx) public view returns (address addr, uint256 tokenId) { + uint256[] storage slots = _metadataStorage[ourTokenId]; uint slotNumber = idx / 4; uint shift = (idx % 4) * 64; uint64 attributeId = uint64(slots[slotNumber] >> shift); return getReferenceAddressAndTokenId(attributeId); } - function loadAllReferences(uint256 tokenId) public view returns (address[] memory addresses, uint256[] memory tokenIds) { + function loadAllStaticReferences(uint256 tokenId) public view override returns (address[] memory addresses, uint256[] memory tokenIds) { uint256[] storage slots = _metadataStorage[tokenId]; addresses = new address[](8); tokenIds = new uint256[](8); diff --git a/test/nfts/TestPatchworkNFT.sol b/test/nfts/TestPatchworkNFT.sol index 6bd0b4a..3914fb5 100644 --- a/test/nfts/TestPatchworkNFT.sol +++ b/test/nfts/TestPatchworkNFT.sol @@ -22,7 +22,7 @@ contract TestPatchworkNFT is PatchworkNFT { function schema() pure external returns (MetadataSchema memory) { MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](1); - entries[0] = MetadataSchemaEntry(1, 0, FieldType.UINT256, 0, FieldVisibility.PUBLIC, 2, 0, "thing"); + entries[0] = MetadataSchemaEntry(1, 0, FieldType.UINT256, 1, FieldVisibility.PUBLIC, 2, 0, "thing"); return MetadataSchema(1, entries); } From f91df4ff6fd384e592a05a1e03cab965d3158253 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Tue, 14 Nov 2023 14:41:09 -0800 Subject: [PATCH 19/63] Multi ref pools (#42) * Basic refactoring and API changes done to enable multi ref pools * coverage --- src/IPatchworkLiteRef.sol | 24 ++++++ src/IPatchworkProtocol.sol | 50 +++++++++++ src/PatchworkProtocol.sol | 102 ++++++++++++++++++++--- test/PatchworkProtocol.t.sol | 14 ++-- test/TestDynamicArrayLiteRefNFT.t.sol | 5 ++ test/TestPatchLiteRefNFT.t.sol | 5 ++ test/nfts/TestDynamicArrayLiteRefNFT.sol | 22 +++++ test/nfts/TestFragmentLiteRefNFT.sol | 22 +++++ test/nfts/TestPatchLiteRefNFT.sol | 22 +++++ 9 files changed, 248 insertions(+), 18 deletions(-) diff --git a/src/IPatchworkLiteRef.sol b/src/IPatchworkLiteRef.sol index d1737ba..cb352cf 100644 --- a/src/IPatchworkLiteRef.sol +++ b/src/IPatchworkLiteRef.sol @@ -102,6 +102,30 @@ interface IPatchworkLiteRef { */ function removeReference(uint256 tokenId, uint64 liteRef) external; + /** + @notice Adds a reference to a token + @param tokenId ID of the token + @param referenceAddress Reference address to add + @param targetMetadataId The metadata ID on the target NFT to unassign from + */ + function addReferenceDirect(uint256 tokenId, uint64 referenceAddress, uint256 targetMetadataId) external; + + /** + @notice Adds multiple references to a token + @param tokenId ID of the token + @param liteRefs Array of lite references to add + @param targetMetadataId The metadata ID on the target NFT to unassign from + */ + function batchAddReferencesDirect(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) external; + + /** + @notice Removes a reference from a token + @param tokenId ID of the token + @param liteRef Lite reference to remove + @param targetMetadataId The metadata ID on the target NFT to unassign from + */ + function removeReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) external; + /** @notice Loads a reference address and token ID at a given index @param ourTokenId ID of the token diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index 01a5394..92faf13 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -512,6 +512,16 @@ interface IPatchworkProtocol { */ function assignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external; + /** + @notice Assigns an NFT relation to have an IPatchworkLiteRef form a LiteRef to a IPatchworkAssignableNFT + @param fragment The IPatchworkAssignableNFT address to assign + @param fragmentTokenId The IPatchworkAssignableNFT Token ID to assign + @param target The IPatchworkLiteRef address to hold the reference to the fragment + @param targetTokenId The IPatchworkLiteRef Token ID to hold the reference to the fragment + @param targetMetadataId The metadata ID on the target NFT to store the reference in + */ + function assignNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) external; + /** @notice Assign multiple NFT fragments to a target NFT in batch @param fragments The array of addresses of the fragment IPatchworkAssignableNFTs @@ -521,6 +531,16 @@ interface IPatchworkProtocol { */ function batchAssignNFT(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) external; + /** + @notice Assign multiple NFT fragments to a target NFT in batch + @param fragments The array of addresses of the fragment IPatchworkAssignableNFTs + @param tokenIds The array of token IDs of the fragment IPatchworkAssignableNFTs + @param target The address of the target IPatchworkLiteRef NFT + @param targetTokenId The token ID of the target IPatchworkLiteRef NFT + @param targetMetadataId The metadata ID on the target NFT to store the references in + */ + function batchAssignNFTDirect(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId, uint256 targetMetadataId) external; + /** @notice Unassign a NFT fragment from a target NFT @param fragment The IPatchworkSingleAssignableNFT address of the fragment NFT @@ -548,6 +568,36 @@ interface IPatchworkProtocol { */ function unassignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external; + /** + @notice Unassign a NFT fragment from a target NFT + @param fragment The IPatchworkSingleAssignableNFT address of the fragment NFT + @param fragmentTokenId The IPatchworkSingleAssignableNFT token ID of the fragment NFT + @param targetMetadataId The metadata ID on the target NFT to unassign from + @dev reverts if fragment is not an IPatchworkSingleAssignableNFT + */ + function unassignSingleNFTDirect(address fragment, uint fragmentTokenId, uint256 targetMetadataId) external; + + /** + @notice Unassigns a multi NFT relation + @param fragment The IPatchworMultiAssignableNFT address to unassign + @param fragmentTokenId The IPatchworkMultiAssignableNFT Token ID to unassign + @param target The IPatchworkLiteRef address which holds a reference to the fragment + @param targetTokenId The IPatchworkLiteRef Token ID which holds a reference to the fragment + @param targetMetadataId The metadata ID on the target NFT to unassign from + @dev reverts if fragment is not an IPatchworkMultiAssignableNFT + */ + function unassignMultiNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) external; + + /** + @notice Unassigns an NFT relation (single or multi) + @param fragment The IPatchworkAssignableNFT address to unassign + @param fragmentTokenId The IPatchworkAssignableNFT Token ID to unassign + @param target The IPatchworkLiteRef address which holds a reference to the fragment + @param targetTokenId The IPatchworkLiteRef Token ID which holds a reference to the fragment + @param targetMetadataId The metadata ID on the target NFT to unassign from + */ + function unassignNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) external; + /** @notice Apply transfer rules and actions of a specific token from one address to another @param from The address of the sender diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 57cfbc6..7d30a39 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -230,25 +230,48 @@ contract PatchworkProtocol is IPatchworkProtocol { function assignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { address targetOwner = IERC721(target).ownerOf(targetTokenId); uint64 ref = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); - // call addReference on the target IPatchworkLiteRef(target).addReference(targetTokenId, ref); } + /** + @dev See {IPatchworkProtocol-assignNFTDirect} + */ + function assignNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { + address targetOwner = IERC721(target).ownerOf(targetTokenId); + uint64 ref = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); + IPatchworkLiteRef(target).addReferenceDirect(targetTokenId, ref, targetMetadataId); + } + /** @dev See {IPatchworkProtocol-batchAssignNFT} */ function batchAssignNFT(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) public mustNotBeFrozen(target, targetTokenId) { + (uint64[] memory refs, ) = _batchAssignCommon(fragments, tokenIds, target, targetTokenId); + IPatchworkLiteRef(target).batchAddReferences(targetTokenId, refs); + } + + /** + @dev See {IPatchworkProtocol-batchAssignNFTDirect} + */ + function batchAssignNFTDirect(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { + (uint64[] memory refs, ) = _batchAssignCommon(fragments, tokenIds, target, targetTokenId); + IPatchworkLiteRef(target).batchAddReferencesDirect(targetTokenId, refs, targetMetadataId); + } + + /** + @dev Common function to handle the batch assignment of NFTs. + */ + function _batchAssignCommon(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) private returns (uint64[] memory refs, address targetOwner) { if (fragments.length != tokenIds.length) { revert BadInputLengths(); } - address targetOwner = IERC721(target).ownerOf(targetTokenId); - uint64[] memory refs = new uint64[](fragments.length); + targetOwner = IERC721(target).ownerOf(targetTokenId); + refs = new uint64[](fragments.length); for (uint i = 0; i < fragments.length; i++) { address fragment = fragments[i]; uint256 fragmentTokenId = tokenIds[i]; refs[i] = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); } - IPatchworkLiteRef(target).batchAddReferences(targetTokenId, refs); } /** @@ -320,15 +343,37 @@ contract PatchworkProtocol is IPatchworkProtocol { /** @dev See {IPatchworkProtocol-unassignNFT} */ - function unassignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public { + function unassignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { + _unassignNFT(fragment, fragmentTokenId, target, targetTokenId, false, 0); + } + + /** + @dev See {IPatchworkProtocol-unassignNFTDirect} + */ + function unassignNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { + _unassignNFT(fragment, fragmentTokenId, target, targetTokenId, true, targetMetadataId); + } + + /** + @dev Common function to handle the unassignment of NFTs. + */ + function _unassignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool isDirect, uint256 targetMetadataId) private { if (IERC165(fragment).supportsInterface(type(IPatchworkMultiAssignableNFT).interfaceId)) { - unassignMultiNFT(fragment, fragmentTokenId, target, targetTokenId); + if (isDirect) { + unassignMultiNFTDirect(fragment, fragmentTokenId, target, targetTokenId, targetMetadataId); + } else { + unassignMultiNFT(fragment, fragmentTokenId, target, targetTokenId); + } } else if (IERC165(fragment).supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)) { (address _target, uint256 _targetTokenId) = IPatchworkSingleAssignableNFT(fragment).getAssignedTo(fragmentTokenId); if (target != _target || _targetTokenId != targetTokenId) { revert FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); } - unassignSingleNFT(fragment, fragmentTokenId); + if (isDirect) { + unassignSingleNFTDirect(fragment, fragmentTokenId, targetMetadataId); + } else { + unassignSingleNFT(fragment, fragmentTokenId); + } } else { revert UnsupportedContract(); } @@ -338,26 +383,54 @@ contract PatchworkProtocol is IPatchworkProtocol { @dev See {IPatchworkProtocol-unassignMultiNFT} */ function unassignMultiNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { + _unassignMultiCommon(fragment, fragmentTokenId, target, targetTokenId, false, 0); + } + + /** + @dev See {IPatchworkProtocol-unassignMultiNFTDirect} + */ + function unassignMultiNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { + _unassignMultiCommon(fragment, fragmentTokenId, target, targetTokenId, true, targetMetadataId); + } + + /** + @dev Common function to handle the unassignment of multi NFTs. + */ + function _unassignMultiCommon(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool isDirect, uint256 targetMetadataId) private { IPatchworkMultiAssignableNFT assignable = IPatchworkMultiAssignableNFT(fragment); string memory scopeName = assignable.getScopeName(); if (!assignable.isAssignedTo(fragmentTokenId, target, targetTokenId)) { revert FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); } - _doUnassign(fragment, fragmentTokenId, target, targetTokenId, scopeName); + _doUnassign(fragment, fragmentTokenId, target, targetTokenId, isDirect, targetMetadataId, scopeName); assignable.unassign(fragmentTokenId, target, targetTokenId); } /** - @dev See {IPatchworkProtocol-unassignNFT} + @dev See {IPatchworkProtocol-unassignSingleNFT} */ function unassignSingleNFT(address fragment, uint fragmentTokenId) public mustNotBeFrozen(fragment, fragmentTokenId) { + _unassignSingleCommon(fragment, fragmentTokenId, false, 0); + } + + /** + @dev See {IPatchworkProtocol-unassignSingleNFTDirect} + */ + function unassignSingleNFTDirect(address fragment, uint fragmentTokenId, uint256 targetMetadataId) public mustNotBeFrozen(fragment, fragmentTokenId) { + _unassignSingleCommon(fragment, fragmentTokenId, true, targetMetadataId); + } + + /** + @dev Common function to handle the unassignment of single NFTs. + */ + function _unassignSingleCommon(address fragment, uint fragmentTokenId, bool isDirect, uint256 targetMetadataId) private { IPatchworkSingleAssignableNFT assignableNFT = IPatchworkSingleAssignableNFT(fragment); string memory scopeName = assignableNFT.getScopeName(); (address target, uint256 targetTokenId) = assignableNFT.getAssignedTo(fragmentTokenId); if (target == address(0)) { revert FragmentNotAssigned(fragment, fragmentTokenId); } - _doUnassign(fragment, fragmentTokenId, target, targetTokenId, scopeName); + _doUnassign(fragment, fragmentTokenId, target, targetTokenId, isDirect, targetMetadataId, scopeName); assignableNFT.unassign(fragmentTokenId); } @@ -369,7 +442,7 @@ contract PatchworkProtocol is IPatchworkProtocol { @param targetTokenId the IPatchworkLiteRef target's tokenId @param scopeName the name of the assignable's scope */ - function _doUnassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, string memory scopeName) private { + function _doUnassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool direct, uint256 targetMetadataId, string memory scopeName) private { Scope storage scope = _mustHaveScope(scopeName); if (scope.owner == msg.sender || scope.operators[msg.sender]) { // continue @@ -390,7 +463,12 @@ contract PatchworkProtocol is IPatchworkProtocol { revert RefNotFound(target, fragment, fragmentTokenId); } delete _liteRefs[targetRef]; - IPatchworkLiteRef(target).removeReference(targetTokenId, ref); + if (direct) { + IPatchworkLiteRef(target).removeReferenceDirect(targetTokenId, ref, targetMetadataId); + } else { + IPatchworkLiteRef(target).removeReference(targetTokenId, ref); + } + emit Unassign(IERC721(target).ownerOf(targetTokenId), fragment, fragmentTokenId, target, targetTokenId); } diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index 0903d55..8f5f520 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -224,10 +224,10 @@ contract PatchworkProtocolTest is Test { vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); // cover called from non-owner/op with no allowUserAssign - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignNFTDirect(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId, 0); vm.startPrank(_scopeOwner); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignNFTDirect(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId, 0); (address addr, uint256 tokenId) = _testFragmentLiteRefNFT.getAssignedTo(fragmentTokenId); assertEq(addr, address(_testPatchLiteRefNFT)); assertEq(tokenId, patchTokenId); @@ -275,7 +275,7 @@ contract PatchworkProtocolTest is Test { vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, "foo")); _prot.unassignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId1, address(_testFragmentLiteRefNFT), fragmentTokenId2); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, "foo")); - _prot.unassignNFT(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2); + _prot.unassignNFTDirect(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2, 0); } function testUnsupportedNFTUnassign() public { @@ -344,7 +344,7 @@ contract PatchworkProtocolTest is Test { vm.prank(_user2Address); _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragmentTokenId); vm.startPrank(_userAddress); - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragmentTokenId); + _prot.unassignSingleNFTDirect(address(_testFragmentLiteRefNFT), fragmentTokenId, 0); // not currently assigned vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssigned.selector, address(_testFragmentLiteRefNFT), fragmentTokenId)); _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragmentTokenId); @@ -394,7 +394,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); // Now Id1 -> Id, unassign Id1 from Id - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment1); + _prot.unassignNFTDirect(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId, 0); // Assign Id1 -> Id _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); // Assign Id2 -> Id1 @@ -441,6 +441,8 @@ contract PatchworkProtocolTest is Test { vm.expectRevert(); // not unassignable _prot.unassignMultiNFT(address(1), 1, address(1), 1); + vm.expectRevert(); // not unassignable + _prot.unassignMultiNFTDirect(address(1), 1, address(1), 1, 0); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); uint256 fragment1 = _testMultiFragmentNFT.mint(_userAddress); @@ -557,7 +559,7 @@ contract PatchworkProtocolTest is Test { // finally a positive test case vm.prank(_scopeOwner); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.batchAssignNFTDirect(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId, 0); for (uint8 i = 0; i < 8; i++) { (address addr, uint256 tokenId) = _testFragmentLiteRefNFT.getAssignedTo(fragments[i]); diff --git a/test/TestDynamicArrayLiteRefNFT.t.sol b/test/TestDynamicArrayLiteRefNFT.t.sol index 1f97f90..60daeac 100644 --- a/test/TestDynamicArrayLiteRefNFT.t.sol +++ b/test/TestDynamicArrayLiteRefNFT.t.sol @@ -108,5 +108,10 @@ contract PatchworkAccountPatchTest is Test { } } + function testUnusedFuncs() public { + TestDynamicArrayLiteRefNFT nft = new TestDynamicArrayLiteRefNFT(address(_prot)); + nft.loadAllStaticReferences(0); + } + } \ No newline at end of file diff --git a/test/TestPatchLiteRefNFT.t.sol b/test/TestPatchLiteRefNFT.t.sol index c88e937..2975f25 100644 --- a/test/TestPatchLiteRefNFT.t.sol +++ b/test/TestPatchLiteRefNFT.t.sol @@ -127,4 +127,9 @@ contract TestPatchLiteRefNFTTest is Test { assertEq(testNFT.loadXP(1), 65535); assertEq(testNFT.loadLevel(1), 255); } + + function testUnusedFuncs() public { + testNFT.loadDynamicReferencePage(0, 0, 0); + testNFT.getDynamicReferenceCount(0); + } } diff --git a/test/nfts/TestDynamicArrayLiteRefNFT.sol b/test/nfts/TestDynamicArrayLiteRefNFT.sol index ebdbc9e..3d0b0a9 100644 --- a/test/nfts/TestDynamicArrayLiteRefNFT.sol +++ b/test/nfts/TestDynamicArrayLiteRefNFT.sol @@ -275,6 +275,28 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef { } } + function addReferenceDirect(uint256 tokenId, uint64 referenceAddress, uint256 targetMetadataId) public override { + if (targetMetadataId != 0) { + revert("Unsupported metadata ID"); + } + addReference(tokenId, referenceAddress); + } + + + function removeReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { + if (targetMetadataId != 0) { + revert("Unsupported metadata ID"); + } + removeReference(tokenId, liteRef); + } + + function batchAddReferencesDirect(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) public override { + if (targetMetadataId != 0) { + revert("Unsupported metadata ID"); + } + batchAddReferences(tokenId, liteRefs); + } + function loadReferenceAddressAndTokenId(uint256 ourTokenId, uint256 idx) public view returns (address addr, uint256 tokenId) { uint256[] storage slots = _dynamicLiterefStorage[ourTokenId].slots; uint slotNumber = idx / 4; // integer division will get the correct slot number diff --git a/test/nfts/TestFragmentLiteRefNFT.sol b/test/nfts/TestFragmentLiteRefNFT.sol index 151ca35..7ff9b8e 100644 --- a/test/nfts/TestFragmentLiteRefNFT.sol +++ b/test/nfts/TestFragmentLiteRefNFT.sol @@ -139,6 +139,28 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef { } } + function addReferenceDirect(uint256 tokenId, uint64 referenceAddress, uint256 targetMetadataId) public override { + if (targetMetadataId != 0) { + revert("Unsupported metadata ID"); + } + addReference(tokenId, referenceAddress); + } + + + function removeReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { + if (targetMetadataId != 0) { + revert("Unsupported metadata ID"); + } + removeReference(tokenId, liteRef); + } + + function batchAddReferencesDirect(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) public view override { + if (targetMetadataId != 0) { + revert("Unsupported metadata ID"); + } + batchAddReferences(tokenId, liteRefs); + } + function batchAddReferences(uint256 ourTokenId, uint64[] calldata /*_referenceAddresses*/) public view override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); // TODO bulk insert for fewer stores diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index 43bb396..7528e5a 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -213,6 +213,28 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { } } + function addReferenceDirect(uint256 tokenId, uint64 referenceAddress, uint256 targetMetadataId) public override { + if (targetMetadataId != 0) { + revert("Unsupported metadata ID"); + } + addReference(tokenId, referenceAddress); + } + + + function removeReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { + if (targetMetadataId != 0) { + revert("Unsupported metadata ID"); + } + removeReference(tokenId, liteRef); + } + + function batchAddReferencesDirect(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) public view override { + if (targetMetadataId != 0) { + revert("Unsupported metadata ID"); + } + batchAddReferences(tokenId, liteRefs); + } + function loadReferenceAddressAndTokenId(uint256 ourTokenId, uint256 idx) public view returns (address addr, uint256 tokenId) { uint256[] storage slots = _metadataStorage[ourTokenId]; uint slotNumber = idx / 4; From 842f944f201f3d9b4811e1a667e5b841ffce5471 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Wed, 15 Nov 2023 13:56:14 -0800 Subject: [PATCH 20/63] hotfix v1.1 --- src/PatchworkFragmentSingle.sol | 2 +- test/nfts/TestDynamicArrayLiteRefNFT.sol | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/PatchworkFragmentSingle.sol b/src/PatchworkFragmentSingle.sol index ab4f0b5..d6b114f 100644 --- a/src/PatchworkFragmentSingle.sol +++ b/src/PatchworkFragmentSingle.sol @@ -64,7 +64,7 @@ abstract contract PatchworkFragmentSingle is PatchworkNFT, IPatchworkSingleAssig /** @dev See {IPatchworAssignable-allowAssignment} */ - function allowAssignment(uint256 ourTokenId, address /*target*/, uint256 /*targetTokenId*/, address targetOwner, address /*by*/, string memory /*scopeName*/) public view returns (bool) { + function allowAssignment(uint256 ourTokenId, address /*target*/, uint256 /*targetTokenId*/, address targetOwner, address /*by*/, string memory /*scopeName*/) virtual public view returns (bool) { // By default only allow single assignments to be to the same owner as the target // Warning - Changing this without changing the other ownership logic in this contract to reflect this will make ownership inconsistent return targetOwner == ownerOf(ourTokenId); diff --git a/test/nfts/TestDynamicArrayLiteRefNFT.sol b/test/nfts/TestDynamicArrayLiteRefNFT.sol index 3d0b0a9..1e3677c 100644 --- a/test/nfts/TestDynamicArrayLiteRefNFT.sol +++ b/test/nfts/TestDynamicArrayLiteRefNFT.sol @@ -75,14 +75,14 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef { */ function schema() pure external override returns (MetadataSchema memory) { MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](8); - entries[0] = MetadataSchemaEntry(0, 0, FieldType.UINT64, 0, FieldVisibility.PUBLIC, 0, 0, "artifactIDs"); // Dynamic - entries[1] = MetadataSchemaEntry(1, 1, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 0, "xp"); - entries[2] = MetadataSchemaEntry(2, 2, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 2, 16, "level"); - entries[3] = MetadataSchemaEntry(3, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 24, "xpLost"); - entries[4] = MetadataSchemaEntry(4, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 40, "stakedMade"); - entries[5] = MetadataSchemaEntry(5, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 56, "stakedCorrect"); - entries[6] = MetadataSchemaEntry(6, 0, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 2, 72, "evolution"); - entries[7] = MetadataSchemaEntry(7, 0, FieldType.CHAR16, 1, FieldVisibility.PUBLIC, 2, 80, "nickname"); + entries[0] = MetadataSchemaEntry(0, 0, FieldType.LITEREF, 0, FieldVisibility.PUBLIC, 0, 0, "artifactIDs"); // Dynamic + entries[1] = MetadataSchemaEntry(1, 1, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 0, 0, "xp"); + entries[2] = MetadataSchemaEntry(2, 2, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 0, 16, "level"); + entries[3] = MetadataSchemaEntry(3, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 0, 24, "xpLost"); + entries[4] = MetadataSchemaEntry(4, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 0, 40, "stakedMade"); + entries[5] = MetadataSchemaEntry(5, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 0, 56, "stakedCorrect"); + entries[6] = MetadataSchemaEntry(6, 0, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 0, 72, "evolution"); + entries[7] = MetadataSchemaEntry(7, 0, FieldType.CHAR16, 1, FieldVisibility.PUBLIC, 0, 80, "nickname"); return MetadataSchema(1, entries); } From a254a076f338b38f50d854b3f937890341a41da8 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Mon, 20 Nov 2023 15:50:02 -0800 Subject: [PATCH 21/63] Reverse lookups (#43) * Implementation for all * test coverage --- src/IPatchwork1155Patch.sol | 10 ++++++++++ src/IPatchworkAccountPatch.sol | 8 ++++++++ src/IPatchworkPatch.sol | 9 +++++++++ src/Patchwork1155Patch.sol | 16 +++++++++++++++- src/PatchworkAccountPatch.sol | 17 ++++++++++++++++- src/PatchworkNFT.sol | 7 +++++++ src/PatchworkPatch.sol | 16 +++++++++++++++- test/Patchwork1155Patch.t.sol | 25 +++++++++++++++++++------ test/PatchworkAccountPatch.t.sol | 21 +++++++++++++++------ test/PatchworkPatch.t.sol | 4 ++++ test/nfts/Test1155PatchNFT.sol | 6 ++++-- test/nfts/TestAccountPatchNFT.sol | 6 ++++-- test/nfts/TestPatchFragmentNFT.sol | 2 +- test/nfts/TestPatchLiteRefNFT.sol | 2 +- 14 files changed, 128 insertions(+), 21 deletions(-) diff --git a/src/IPatchwork1155Patch.sol b/src/IPatchwork1155Patch.sol index b517ef4..9ffed15 100644 --- a/src/IPatchwork1155Patch.sol +++ b/src/IPatchwork1155Patch.sol @@ -22,4 +22,14 @@ interface IPatchwork1155Patch { @return tokenId ID of the newly minted token */ function mintPatch(address to, address originalNFTAddress, uint256 originalNFTTokenId, address originalAccount) external returns (uint256 tokenId); + + /** + @notice Returns the token ID (if it exists) for an NFT that may have been patched + @dev Requires reverse storage enabled + @param originalNFTAddress Address of the original NFT + @param originalNFTTokenId ID of the original NFT token + @param originalAccount Address of the original 1155 account + @return tokenId ID of the newly minted token + */ + function getTokenIdForOriginalNFT(address originalNFTAddress, uint256 originalNFTTokenId, address originalAccount) external returns (uint256 tokenId); } \ No newline at end of file diff --git a/src/IPatchworkAccountPatch.sol b/src/IPatchworkAccountPatch.sol index 13d8916..e9e48e4 100644 --- a/src/IPatchworkAccountPatch.sol +++ b/src/IPatchworkAccountPatch.sol @@ -20,4 +20,12 @@ interface IPatchworkAccountPatch { @return tokenId ID of the newly minted token */ function mintPatch(address owner, address originalAccountAddress) external returns (uint256 tokenId); + + /** + @notice Returns the token ID (if it exists) for an NFT that may have been patched + @dev Requires reverse storage enabled + @param originalAddress Address of the original account + @return tokenId ID of the newly minted token + */ + function getTokenIdForOriginalAccount(address originalAddress) external returns (uint256 tokenId); } \ No newline at end of file diff --git a/src/IPatchworkPatch.sol b/src/IPatchworkPatch.sol index 3f9e1d2..69ddb8e 100644 --- a/src/IPatchworkPatch.sol +++ b/src/IPatchworkPatch.sol @@ -34,4 +34,13 @@ interface IPatchworkPatch { @return address Address of the owner */ function unpatchedOwnerOf(uint256 tokenId) external returns (address); + + /** + @notice Returns the token ID (if it exists) for an NFT that may have been patched + @dev Requires reverse storage enabled + @param originalNFTAddress Address of the original NFT + @param originalNFTTokenId ID of the original NFT token + @return tokenId ID of the newly minted token + */ + function getTokenIdForOriginalNFT(address originalNFTAddress, uint256 originalNFTTokenId) external returns (uint256 tokenId); } \ No newline at end of file diff --git a/src/Patchwork1155Patch.sol b/src/Patchwork1155Patch.sol index ef3bd87..56ee210 100644 --- a/src/Patchwork1155Patch.sol +++ b/src/Patchwork1155Patch.sol @@ -20,6 +20,9 @@ abstract contract Patchwork1155Patch is PatchworkNFT, IPatchwork1155Patch { /// @dev Mapping from token ID to the canonical address of the NFT that this patch is applied to. mapping(uint256 => PatchCanonical) internal _patchedAddresses; + /// @dev Mapping of hash of original address + token ID + account for reverse lookups + mapping(bytes32 => uint256) internal _patchedAddressesRev; // hash of patched addr+tokenid+account to tokenId + /** @dev See {IERC165-supportsInterface} */ @@ -40,10 +43,21 @@ abstract contract Patchwork1155Patch is PatchworkNFT, IPatchwork1155Patch { @param tokenId the tokenId of the patch @param originalNFTAddress the address of the original ERC-1155 we are patching @param originalNFTTokenId the tokenId of the original ERC-1155 we are patching + @param withReverse store reverse lookup @param account the account of the ERC-1155 we are patching */ - function _storePatch(uint256 tokenId, address originalNFTAddress, uint256 originalNFTTokenId, address account) internal virtual { + function _storePatch(uint256 tokenId, address originalNFTAddress, uint256 originalNFTTokenId, address account, bool withReverse) internal virtual { _patchedAddresses[tokenId] = PatchCanonical(originalNFTAddress, originalNFTTokenId, account); + if (withReverse) { + _patchedAddressesRev[keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId, account))] = tokenId; + } + } + + /** + @dev See {IPatchwork1155Patch-getTokenIdForOriginalNFT} + */ + function getTokenIdForOriginalNFT(address originalNFTAddress, uint256 originalNFTTokenId, address originalAccount) public view virtual returns (uint256 tokenId) { + return _patchedAddressesRev[keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId, originalAccount))]; } /** diff --git a/src/PatchworkAccountPatch.sol b/src/PatchworkAccountPatch.sol index cabb097..4ffef1a 100644 --- a/src/PatchworkAccountPatch.sol +++ b/src/PatchworkAccountPatch.sol @@ -15,6 +15,9 @@ abstract contract PatchworkAccountPatch is PatchworkNFT, IPatchworkAccountPatch /// @dev Mapping from token ID to the address of the NFT that this patch is applied to. mapping(uint256 => address) internal _patchedAddresses; + /// @dev Mapping of original address to token Ids for reverse lookups + mapping(address => uint256) internal _patchedAddressesRev; + /** @dev See {IERC165-supportsInterface} */ @@ -34,10 +37,21 @@ abstract contract PatchworkAccountPatch is PatchworkNFT, IPatchworkAccountPatch @notice stores a patch @param tokenId the tokenId of the patch @param originalAccountAddress the account we are patching + @param withReverse store reverse lookup */ - function _storePatch(uint256 tokenId, address originalAccountAddress) internal virtual { + function _storePatch(uint256 tokenId, address originalAccountAddress, bool withReverse) internal virtual { // PatchworkProtocol handles uniqueness assertion _patchedAddresses[tokenId] = originalAccountAddress; + if (withReverse) { + _patchedAddressesRev[originalAccountAddress] = tokenId; + } + } + + /** + @dev See {IPatchworkAccountPatch-getTokenIdForOriginalAccount} + */ + function getTokenIdForOriginalAccount(address originalAddress) public view virtual returns (uint256 tokenId) { + return _patchedAddressesRev[originalAddress]; } /** @@ -46,4 +60,5 @@ abstract contract PatchworkAccountPatch is PatchworkNFT, IPatchworkAccountPatch function _burn(uint256 /*tokenId*/) internal virtual override { revert IPatchworkProtocol.UnsupportedOperation(); } + } \ No newline at end of file diff --git a/src/PatchworkNFT.sol b/src/PatchworkNFT.sol index 55958f2..735b28a 100644 --- a/src/PatchworkNFT.sol +++ b/src/PatchworkNFT.sol @@ -240,4 +240,11 @@ abstract contract PatchworkNFT is ERC721, IPatchworkNFT, IERC4906 { } _; } + + modifier mustBeManager() { + if (msg.sender != _manager) { + revert IPatchworkProtocol.NotAuthorized(msg.sender); + } + _; + } } \ No newline at end of file diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index b9cde07..d59a2b0 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -18,6 +18,9 @@ abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { /// @dev Mapping from token ID to the token ID of the NFT that this patch is applied to. mapping(uint256 => uint256) internal _patchedTokenIds; + /// @dev Mapping of hash of original address + token ID for reverse lookups + mapping(bytes32 => uint256) internal _patchedAddressesRev; // hash of patched addr+tokenid to tokenId + /** @dev See {IERC165-supportsInterface} */ @@ -47,10 +50,21 @@ abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { @param tokenId the tokenId of the patch @param originalNFTAddress the address of the original ERC-721 we are patching @param originalNFTTokenId the tokenId of the original ERC-721 we are patching + @param withReverse store reverse lookup */ - function _storePatch(uint256 tokenId, address originalNFTAddress, uint256 originalNFTTokenId) internal virtual { + function _storePatch(uint256 tokenId, address originalNFTAddress, uint256 originalNFTTokenId, bool withReverse) internal virtual { _patchedAddresses[tokenId] = originalNFTAddress; _patchedTokenIds[tokenId] = originalNFTTokenId; + if (withReverse) { + _patchedAddressesRev[keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId))] = tokenId; + } + } + + /** + @dev See {IPatchworkPatch-getTokenIdForOriginalNFT} + */ + function getTokenIdForOriginalNFT(address originalNFTAddress, uint256 originalNFTTokenId) public view virtual returns (uint256 tokenId) { + return _patchedAddressesRev[keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId))]; } /** diff --git a/test/Patchwork1155Patch.t.sol b/test/Patchwork1155Patch.t.sol index 9190d08..2ec6d6a 100644 --- a/test/Patchwork1155Patch.t.sol +++ b/test/Patchwork1155Patch.t.sol @@ -39,13 +39,13 @@ contract Patchwork1155PatchTest is Test { function testScopeName() public { vm.prank(_scopeOwner); - Test1155PatchNFT testAccountPatchNFT = new Test1155PatchNFT(address(_prot)); + Test1155PatchNFT testAccountPatchNFT = new Test1155PatchNFT(address(_prot), false); assertEq(_scopeName, testAccountPatchNFT.getScopeName()); } function testSupportsInterface() public { vm.prank(_scopeOwner); - Test1155PatchNFT testAccountPatchNFT = new Test1155PatchNFT(address(_prot)); + Test1155PatchNFT testAccountPatchNFT = new Test1155PatchNFT(address(_prot), false); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC165).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC721).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC4906).interfaceId)); @@ -56,7 +56,7 @@ contract Patchwork1155PatchTest is Test { function test1155Patch() public { vm.startPrank(_scopeOwner); - Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot)); + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot), false); TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); vm.stopPrank(); @@ -76,7 +76,7 @@ contract Patchwork1155PatchTest is Test { function test1155PatchProto() public { vm.startPrank(_scopeOwner); - Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot)); + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot), false); TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); @@ -102,7 +102,7 @@ contract Patchwork1155PatchTest is Test { vm.prank(_scopeOwner); _prot.setScopeRules(_scopeName, true, false, false); // Not same owner model, yes transferrable - Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot)); + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot), false); TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); // user can mint @@ -111,10 +111,23 @@ contract Patchwork1155PatchTest is Test { function testBurn() public { vm.startPrank(_scopeOwner); - Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot)); + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot), false); TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedOperation.selector)); test1155PatchNFT.burn(b); } + + function testReverseLookups() public { + vm.startPrank(_scopeOwner); + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot), true); + TestBase1155 base1155 = new TestBase1155(); + uint256 b = base1155.mint(_userAddress, 1, 5); + uint256 pId = _prot.create1155Patch(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); + assertEq(pId, test1155PatchNFT.getTokenIdForOriginalNFT(address(base1155), b, _userAddress)); + // testing not enabled + Test1155PatchNFT test1155PatchNFT2 = new Test1155PatchNFT(address(_prot), false); + // 0 is default / not-enabled + assertEq(0, test1155PatchNFT2.getTokenIdForOriginalNFT(address(base1155), b, _userAddress)); + } } \ No newline at end of file diff --git a/test/PatchworkAccountPatch.t.sol b/test/PatchworkAccountPatch.t.sol index aa2576a..f04e503 100644 --- a/test/PatchworkAccountPatch.t.sol +++ b/test/PatchworkAccountPatch.t.sol @@ -38,13 +38,13 @@ contract PatchworkAccountPatchTest is Test { function testScopeName() public { vm.prank(_scopeOwner); - TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, false); assertEq(_scopeName, testAccountPatchNFT.getScopeName()); } function testSupportsInterface() public { vm.prank(_scopeOwner); - TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, false); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC165).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC721).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC4906).interfaceId)); @@ -56,7 +56,7 @@ contract PatchworkAccountPatchTest is Test { function testAccountPatchNotSameOwner() public { // Not same owner model, yes transferrable vm.prank(_scopeOwner); - TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, false); // User patching is off, not authorized vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); @@ -78,7 +78,7 @@ contract PatchworkAccountPatchTest is Test { function testAccountPatchSameOwner() public { // Same owner model, not transferrable vm.prank(_scopeOwner); - TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), true); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), true, false); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.MintNotAllowed.selector, _userAddress)); vm.prank(_scopeOwner); _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); @@ -94,10 +94,19 @@ contract PatchworkAccountPatchTest is Test { _prot.setScopeRules(_scopeName, true, false, false); // Not same owner model, yes transferrable vm.prank(_scopeOwner); - TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, false); // User patching is on _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); } - + function testReverseLookups() public { + vm.startPrank(_scopeOwner); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, true); + // User patching is on + uint256 pId = _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); + assertEq(pId, testAccountPatchNFT.getTokenIdForOriginalAccount(_user2Address)); + // disabled case + TestAccountPatchNFT testAccountPatchNFT2 = new TestAccountPatchNFT(address(_prot), false, false); + assertEq(0, testAccountPatchNFT2.getTokenIdForOriginalAccount(_user2Address)); + } } \ No newline at end of file diff --git a/test/PatchworkPatch.t.sol b/test/PatchworkPatch.t.sol index 415a6d5..d8e0483 100644 --- a/test/PatchworkPatch.t.sol +++ b/test/PatchworkPatch.t.sol @@ -91,6 +91,10 @@ contract PatchworkPatchTest is Test { uint256 liteRefId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); uint256 liteRefId2 = _prot.createPatch(_user2Address, address(_testBaseNFT), baseTokenId2, address(_testPatchLiteRefNFT)); uint256 fragmentTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId3, address(testPatchFragmentNFT)); + // check reverse lookups - testPatchLiteRefNFT is disabled, always 0. patchFragmentNFT is enabled + assertEq(0, _testPatchLiteRefNFT.getTokenIdForOriginalNFT(address(_testBaseNFT), baseTokenId)); + assertEq(0, _testPatchLiteRefNFT.getTokenIdForOriginalNFT(address(_testBaseNFT), baseTokenId2)); + assertEq(fragmentTokenId, _testPatchLiteRefNFT.getTokenIdForOriginalNFT(address(testPatchFragmentNFT), baseTokenId3)); // cannot assign patch to a literef that this person does not own vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); _prot.assignNFT(address(testPatchFragmentNFT), fragmentTokenId, address(_testPatchLiteRefNFT), liteRefId2); diff --git a/test/nfts/Test1155PatchNFT.sol b/test/nfts/Test1155PatchNFT.sol index 107e545..5bf2b4d 100644 --- a/test/nfts/Test1155PatchNFT.sol +++ b/test/nfts/Test1155PatchNFT.sol @@ -6,12 +6,14 @@ import "../../src/Patchwork1155Patch.sol"; contract Test1155PatchNFT is Patchwork1155Patch { uint256 _nextTokenId = 0; + bool _reverseEnabled = false; struct Test1155PatchNFTMetadata { uint256 thing; } - constructor(address manager_) PatchworkNFT("testscope", "Test1155PatchNFT", "TPLR", msg.sender, manager_) { + constructor(address manager_, bool reverseEnabled_) PatchworkNFT("testscope", "Test1155PatchNFT", "TPLR", msg.sender, manager_) { + _reverseEnabled = reverseEnabled_; } function schemaURI() pure external returns (string memory) { @@ -35,7 +37,7 @@ contract Test1155PatchNFT is Patchwork1155Patch { // Just for testing tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, account); + _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, account, _reverseEnabled); _safeMint(to, tokenId); _metadataStorage[tokenId] = new uint256[](1); return tokenId; diff --git a/test/nfts/TestAccountPatchNFT.sol b/test/nfts/TestAccountPatchNFT.sol index 8af06d3..1f76eac 100644 --- a/test/nfts/TestAccountPatchNFT.sol +++ b/test/nfts/TestAccountPatchNFT.sol @@ -7,13 +7,15 @@ contract TestAccountPatchNFT is PatchworkAccountPatch { uint256 _nextTokenId = 0; bool _sameOwnerModel; + bool _reverseEnabled; struct TestPatchworkNFTMetadata { uint256 thing; } - constructor(address manager_, bool sameOwnerModel_) PatchworkNFT("testscope", "TestAccountPatchNFT", "TPLR", msg.sender, manager_) { + constructor(address manager_, bool sameOwnerModel_, bool reverseEnabled_) PatchworkNFT("testscope", "TestAccountPatchNFT", "TPLR", msg.sender, manager_) { _sameOwnerModel = sameOwnerModel_; + _reverseEnabled = reverseEnabled_; } function schemaURI() pure external returns (string memory) { @@ -41,7 +43,7 @@ contract TestAccountPatchNFT is PatchworkAccountPatch { } uint256 tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, original); + _storePatch(tokenId, original, _reverseEnabled); _mint(to, tokenId); _metadataStorage[tokenId] = new uint256[](1); return tokenId; diff --git a/test/nfts/TestPatchFragmentNFT.sol b/test/nfts/TestPatchFragmentNFT.sol index ecefd92..68096c6 100644 --- a/test/nfts/TestPatchFragmentNFT.sol +++ b/test/nfts/TestPatchFragmentNFT.sol @@ -128,7 +128,7 @@ contract TestPatchFragmentNFT is PatchworkPatch, PatchworkFragmentSingle { // Just for testing tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, originalNFTAddress, originalNFTTokenId); + _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, true); _safeMint(originalNFTOwner, tokenId); _metadataStorage[tokenId] = new uint256[](3); return tokenId; diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index 7528e5a..b8ffd23 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -181,7 +181,7 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { // Just for testing tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, originalNFTAddress, originalNFTTokenId); + _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, false); _safeMint(owner, tokenId); _metadataStorage[tokenId] = new uint256[](3); return tokenId; From 65e43e7353c23fede41e39ae4959eab75540d886 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Mon, 20 Nov 2023 15:50:10 -0800 Subject: [PATCH 22/63] Renamed incorrectly named liteRefs (#44) --- src/IPatchworkLiteRef.sol | 8 +-- src/PatchworkLiteRef.sol | 8 +-- test/TestPatchLiteRefNFT.t.sol | 2 +- test/nfts/TestDynamicArrayLiteRefNFT.sol | 66 ++++++++++++------------ test/nfts/TestFragmentLiteRefNFT.sol | 44 ++++++++-------- test/nfts/TestPatchLiteRefNFT.sol | 42 +++++++-------- 6 files changed, 85 insertions(+), 85 deletions(-) diff --git a/src/IPatchworkLiteRef.sol b/src/IPatchworkLiteRef.sol index cb352cf..61b7740 100644 --- a/src/IPatchworkLiteRef.sol +++ b/src/IPatchworkLiteRef.sol @@ -84,9 +84,9 @@ interface IPatchworkLiteRef { /** @notice Adds a reference to a token @param tokenId ID of the token - @param referenceAddress Reference address to add + @param liteRef LiteRef to add */ - function addReference(uint256 tokenId, uint64 referenceAddress) external; + function addReference(uint256 tokenId, uint64 liteRef) external; /** @notice Adds multiple references to a token @@ -105,10 +105,10 @@ interface IPatchworkLiteRef { /** @notice Adds a reference to a token @param tokenId ID of the token - @param referenceAddress Reference address to add + @param liteRef LiteRef to add @param targetMetadataId The metadata ID on the target NFT to unassign from */ - function addReferenceDirect(uint256 tokenId, uint64 referenceAddress, uint256 targetMetadataId) external; + function addReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) external; /** @notice Adds multiple references to a token diff --git a/src/PatchworkLiteRef.sol b/src/PatchworkLiteRef.sol index 592b736..07c42f5 100644 --- a/src/PatchworkLiteRef.sol +++ b/src/PatchworkLiteRef.sol @@ -96,7 +96,7 @@ abstract contract PatchworkLiteRef is IPatchworkLiteRef, ERC165 { /** @dev See {IPatchworkLiteRef-getLiteReference} */ - function getLiteReference(address addr, uint256 tokenId) public virtual view returns (uint64 referenceAddress, bool redacted) { + function getLiteReference(address addr, uint256 tokenId) public virtual view returns (uint64 liteRef, bool redacted) { uint8 refId = _referenceAddressIds[addr]; if (refId == 0) { return (0, false); @@ -110,10 +110,10 @@ abstract contract PatchworkLiteRef is IPatchworkLiteRef, ERC165 { /** @dev See {IPatchworkLiteRef-getReferenceAddressAndTokenId} */ - function getReferenceAddressAndTokenId(uint64 referenceAddress) public virtual view returns (address addr, uint256 tokenId) { + function getReferenceAddressAndTokenId(uint64 liteRef) public virtual view returns (address addr, uint256 tokenId) { // <8 bits of refId, 56 bits of tokenId> - uint8 refId = uint8(referenceAddress >> 56); - tokenId = referenceAddress & 0x00FFFFFFFFFFFFFF; // 64 bit mask + uint8 refId = uint8(liteRef >> 56); + tokenId = liteRef & 0x00FFFFFFFFFFFFFF; // 64 bit mask return (_referenceAddresses[refId], tokenId); } diff --git a/test/TestPatchLiteRefNFT.t.sol b/test/TestPatchLiteRefNFT.t.sol index 2975f25..5ac29c9 100644 --- a/test/TestPatchLiteRefNFT.t.sol +++ b/test/TestPatchLiteRefNFT.t.sol @@ -128,7 +128,7 @@ contract TestPatchLiteRefNFTTest is Test { assertEq(testNFT.loadLevel(1), 255); } - function testUnusedFuncs() public { + function testUnusedFuncs() public view { testNFT.loadDynamicReferencePage(0, 0, 0); testNFT.getDynamicReferenceCount(0); } diff --git a/test/nfts/TestDynamicArrayLiteRefNFT.sol b/test/nfts/TestDynamicArrayLiteRefNFT.sol index 1e3677c..4547e58 100644 --- a/test/nfts/TestDynamicArrayLiteRefNFT.sol +++ b/test/nfts/TestDynamicArrayLiteRefNFT.sol @@ -144,38 +144,38 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef { return uint16(uint256(_metadataStorage[_tokenId][2]) >> 16); } - function addReference(uint256 ourTokenId, uint64 referenceAddress) public override { + function addReference(uint256 ourTokenId, uint64 liteRef) public override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); // to append: find last slot, if it's not full, add, otherwise start a new slot. DynamicLiteRefs storage store = _dynamicLiterefStorage[ourTokenId]; uint256 slotsLen = store.slots.length; if (slotsLen == 0) { - store.slots.push(uint256(referenceAddress)); - store.idx[referenceAddress] = 0; + store.slots.push(uint256(liteRef)); + store.idx[liteRef] = 0; } else { uint256 slot = store.slots[slotsLen-1]; if (slot >= (1 << 192)) { // new slot (pos 1) - store.slots.push(uint256(referenceAddress)); - store.idx[referenceAddress] = slotsLen; + store.slots.push(uint256(liteRef)); + store.idx[liteRef] = slotsLen; } else { - store.idx[referenceAddress] = slotsLen-1; + store.idx[liteRef] = slotsLen-1; // Reverse search for the next empty subslot if (slot >= (1 << 128)) { // pos 4 - store.slots[slotsLen-1] = slot | uint256(referenceAddress) << 192; + store.slots[slotsLen-1] = slot | uint256(liteRef) << 192; } else if (slot >= (1 << 64)) { // pos 3 - store.slots[slotsLen-1] = slot | uint256(referenceAddress) << 128; + store.slots[slotsLen-1] = slot | uint256(liteRef) << 128; } else { // pos 2 - store.slots[slotsLen-1] = slot | uint256(referenceAddress) << 64; + store.slots[slotsLen-1] = slot | uint256(liteRef) << 64; } } } } - function batchAddReferences(uint256 ourTokenId, uint64[] calldata _referenceAddresses) public override { + function batchAddReferences(uint256 ourTokenId, uint64[] calldata _liteRefs) public override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); // do in batches of 4 with 1 remainder pass DynamicLiteRefs storage store = _dynamicLiterefStorage[ourTokenId]; @@ -183,27 +183,27 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef { if (slotsLen > 0) { revert("already loaded"); } - uint256 fullBatchCount = _referenceAddresses.length / 4; - uint256 remainder = _referenceAddresses.length % 4; + uint256 fullBatchCount = _liteRefs.length / 4; + uint256 remainder = _liteRefs.length % 4; for (uint256 batch = 0; batch < fullBatchCount; batch++) { uint256 refIdx = batch * 4; - uint256 slot = uint256(_referenceAddresses[refIdx]) | (uint256(_referenceAddresses[refIdx+1]) << 64) | (uint256(_referenceAddresses[refIdx+2]) << 128) | (uint256(_referenceAddresses[refIdx+3]) << 192); + uint256 slot = uint256(_liteRefs[refIdx]) | (uint256(_liteRefs[refIdx+1]) << 64) | (uint256(_liteRefs[refIdx+2]) << 128) | (uint256(_liteRefs[refIdx+3]) << 192); store.slots.push(slot); - store.idx[_referenceAddresses[refIdx]] = batch; - store.idx[_referenceAddresses[refIdx + 1]] = batch; - store.idx[_referenceAddresses[refIdx + 2]] = batch; - store.idx[_referenceAddresses[refIdx + 3]] = batch; + store.idx[_liteRefs[refIdx]] = batch; + store.idx[_liteRefs[refIdx + 1]] = batch; + store.idx[_liteRefs[refIdx + 2]] = batch; + store.idx[_liteRefs[refIdx + 3]] = batch; } uint256 rSlot; for (uint256 i = 0; i < remainder; i++) { uint256 idx = (fullBatchCount * 4) + i; - rSlot = rSlot | (uint256(_referenceAddresses[idx]) << (i * 64)); - store.idx[_referenceAddresses[idx]] = fullBatchCount; + rSlot = rSlot | (uint256(_liteRefs[idx]) << (i * 64)); + store.idx[_liteRefs[idx]] = fullBatchCount; } store.slots.push(rSlot); } - function removeReference(uint256 ourTokenId, uint64 referenceAddress) public override { + function removeReference(uint256 ourTokenId, uint64 liteRef) public override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); DynamicLiteRefs storage store = _dynamicLiterefStorage[ourTokenId]; uint256 slotsLen = store.slots.length; @@ -212,15 +212,15 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef { } console.log("removing"); - console.logBytes8(bytes8(referenceAddress)); + console.logBytes8(bytes8(liteRef)); for (uint256 i = 0; i < store.slots.length; i++) { console.logBytes32(bytes32(store.slots[i])); } uint256 count = getDynamicReferenceCount(ourTokenId); if (count == 1) { - if (store.slots[0] == referenceAddress) { + if (store.slots[0] == liteRef) { store.slots.pop(); - delete store.idx[referenceAddress]; + delete store.idx[liteRef]; } else { revert("not found"); } @@ -246,23 +246,23 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef { lastRef = uint64(slot); store.slots.pop(); } - if (lastRef == referenceAddress) { + if (lastRef == liteRef) { // it was the last ref. No need to replace anything. It's already cleared so just clear the index - delete store.idx[referenceAddress]; + delete store.idx[liteRef]; } else { // Find the ref and replace it with lastRef then update indexes - uint256 refSlotIdx = store.idx[referenceAddress]; + uint256 refSlotIdx = store.idx[liteRef]; slot = store.slots[refSlotIdx]; - if (uint64(slot >> 192) == referenceAddress) { + if (uint64(slot >> 192) == liteRef) { slot = slot & 0x0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; slot = slot | (uint256(lastRef) << 192); - } else if (uint64(slot >> 128) == referenceAddress) { + } else if (uint64(slot >> 128) == liteRef) { slot = slot & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; slot = slot | (uint256(lastRef) << 128); - } else if (uint64(slot >> 64) == referenceAddress) { + } else if (uint64(slot >> 64) == liteRef) { slot = slot & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF; slot = slot | (uint256(lastRef) << 64); - } else if (uint64(slot) == referenceAddress) { + } else if (uint64(slot) == liteRef) { slot = slot & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000; slot = slot | uint256(lastRef); } else { @@ -270,16 +270,16 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef { } store.slots[refSlotIdx] = slot; store.idx[lastRef] = refSlotIdx; - delete store.idx[referenceAddress]; + delete store.idx[liteRef]; } } } - function addReferenceDirect(uint256 tokenId, uint64 referenceAddress, uint256 targetMetadataId) public override { + function addReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { if (targetMetadataId != 0) { revert("Unsupported metadata ID"); } - addReference(tokenId, referenceAddress); + addReference(tokenId, liteRef); } diff --git a/test/nfts/TestFragmentLiteRefNFT.sol b/test/nfts/TestFragmentLiteRefNFT.sol index 7ff9b8e..63d4c49 100644 --- a/test/nfts/TestFragmentLiteRefNFT.sol +++ b/test/nfts/TestFragmentLiteRefNFT.sol @@ -113,37 +113,37 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef { return unpackMetadata(_metadataStorage[_tokenId]); } - function addReference(uint256 ourTokenId, uint64 referenceAddress) public override { + function addReference(uint256 ourTokenId, uint64 liteRef) public override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); uint256[] storage mdStorage = _metadataStorage[ourTokenId]; uint256 slot = mdStorage[0]; uint256 slot2 = mdStorage[1]; if (uint64(slot) == 0) { - mdStorage[0] = slot | referenceAddress; + mdStorage[0] = slot | liteRef; } else if (uint64(slot >> 64) == 0) { - mdStorage[0] = slot | uint256(referenceAddress) << 64; + mdStorage[0] = slot | uint256(liteRef) << 64; } else if (uint64(slot >> 128) == 0) { - mdStorage[0] = slot | uint256(referenceAddress) << 128; + mdStorage[0] = slot | uint256(liteRef) << 128; } else if (uint64(slot >> 192) == 0) { - mdStorage[0] = slot | uint256(referenceAddress) << 192; + mdStorage[0] = slot | uint256(liteRef) << 192; } else if (uint64(slot2) == 0) { - mdStorage[0] = slot2 | referenceAddress; + mdStorage[0] = slot2 | liteRef; } else if (uint64(slot2 >> 64) == 0) { - mdStorage[0] = slot2 | uint256(referenceAddress) << 64; + mdStorage[0] = slot2 | uint256(liteRef) << 64; } else if (uint64(slot2 >> 128) == 0) { - mdStorage[0] = slot2 | uint256(referenceAddress) << 128; + mdStorage[0] = slot2 | uint256(liteRef) << 128; } else if (uint64(slot2 >> 192) == 0) { - mdStorage[0] = slot2 | uint256(referenceAddress) << 192; + mdStorage[0] = slot2 | uint256(liteRef) << 192; } else { revert("No reference slots available"); } } - function addReferenceDirect(uint256 tokenId, uint64 referenceAddress, uint256 targetMetadataId) public override { + function addReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { if (targetMetadataId != 0) { revert("Unsupported metadata ID"); } - addReference(tokenId, referenceAddress); + addReference(tokenId, liteRef); } @@ -161,31 +161,31 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef { batchAddReferences(tokenId, liteRefs); } - function batchAddReferences(uint256 ourTokenId, uint64[] calldata /*_referenceAddresses*/) public view override { + function batchAddReferences(uint256 ourTokenId, uint64[] calldata /*_liteRefs*/) public view override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); // TODO bulk insert for fewer stores } - function removeReference(uint256 ourTokenId, uint64 referenceAddress) public override { + function removeReference(uint256 ourTokenId, uint64 liteRef) public override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); uint256[] storage mdStorage = _metadataStorage[ourTokenId]; uint256 slot = mdStorage[0]; uint256 slot2 = mdStorage[1]; - if (uint64(slot) == referenceAddress) { + if (uint64(slot) == liteRef) { mdStorage[0] = slot & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000; - } else if (uint64(slot >> 64) == referenceAddress) { + } else if (uint64(slot >> 64) == liteRef) { mdStorage[0] = slot & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF; - } else if (uint64(slot >> 128) == referenceAddress) { + } else if (uint64(slot >> 128) == liteRef) { mdStorage[0] = slot & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - } else if (uint64(slot >> 192) == referenceAddress) { + } else if (uint64(slot >> 192) == liteRef) { mdStorage[0] = slot & 0x0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - } else if (uint64(slot2) == referenceAddress) { + } else if (uint64(slot2) == liteRef) { mdStorage[0] = slot2 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000; - } else if (uint64(slot2 >> 64) == referenceAddress) { + } else if (uint64(slot2 >> 64) == liteRef) { mdStorage[0] = slot2 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF; - } else if (uint64(slot2 >> 128) == referenceAddress) { + } else if (uint64(slot2 >> 128) == liteRef) { mdStorage[0] = slot2 & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - } else if (uint64(slot2 >> 192) == referenceAddress) { + } else if (uint64(slot2 >> 192) == liteRef) { mdStorage[0] = slot2 & 0x0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; } else { revert("not assigned"); @@ -239,7 +239,7 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef { } // Testing overrides - function getLiteReference(address addr, uint256 tokenId) public virtual override view returns (uint64 referenceAddress, bool redacted) { + function getLiteReference(address addr, uint256 tokenId) public virtual override view returns (uint64 liteRef, bool redacted) { if (_getLiteRefOverrideSet) { return (_getLiteRefOverride, false); } diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index b8ffd23..ff32213 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -139,33 +139,33 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { return uint16(uint256(_metadataStorage[_tokenId][2]) >> 16); } - function addReference(uint256 ourTokenId, uint64 referenceAddress) public override { + function addReference(uint256 ourTokenId, uint64 liteRef) public override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); uint256[] storage mdStorage = _metadataStorage[ourTokenId]; uint256 slot = mdStorage[0]; uint256 slot2 = mdStorage[1]; if (uint64(slot) == 0) { - mdStorage[0] = slot | referenceAddress; + mdStorage[0] = slot | liteRef; } else if (uint64(slot >> 64) == 0) { - mdStorage[0] = slot | uint256(referenceAddress) << 64; + mdStorage[0] = slot | uint256(liteRef) << 64; } else if (uint64(slot >> 128) == 0) { - mdStorage[0] = slot | uint256(referenceAddress) << 128; + mdStorage[0] = slot | uint256(liteRef) << 128; } else if (uint64(slot >> 192) == 0) { - mdStorage[0] = slot | uint256(referenceAddress) << 192; + mdStorage[0] = slot | uint256(liteRef) << 192; } else if (uint64(slot2) == 0) { - mdStorage[0] = slot2 | referenceAddress; + mdStorage[0] = slot2 | liteRef; } else if (uint64(slot2 >> 64) == 0) { - mdStorage[0] = slot2 | uint256(referenceAddress) << 64; + mdStorage[0] = slot2 | uint256(liteRef) << 64; } else if (uint64(slot2 >> 128) == 0) { - mdStorage[0] = slot2 | uint256(referenceAddress) << 128; + mdStorage[0] = slot2 | uint256(liteRef) << 128; } else if (uint64(slot2 >> 192) == 0) { - mdStorage[0] = slot2 | uint256(referenceAddress) << 192; + mdStorage[0] = slot2 | uint256(liteRef) << 192; } else { revert("No reference slots available"); } } - function batchAddReferences(uint256 ourTokenId, uint64[] calldata /*_referenceAddresses*/) public view override { + function batchAddReferences(uint256 ourTokenId, uint64[] calldata /*_liteRefs*/) public view override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); // TODO bulk insert for fewer stores } @@ -187,37 +187,37 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { return tokenId; } - function removeReference(uint256 ourTokenId, uint64 referenceAddress) public override { + function removeReference(uint256 ourTokenId, uint64 liteRef) public override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); uint256[] storage mdStorage = _metadataStorage[ourTokenId]; uint256 slot = mdStorage[0]; uint256 slot2 = mdStorage[1]; - if (uint64(slot) == referenceAddress) { + if (uint64(slot) == liteRef) { mdStorage[0] = slot & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000; - } else if (uint64(slot >> 64) == referenceAddress) { + } else if (uint64(slot >> 64) == liteRef) { mdStorage[0] = slot & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF; - } else if (uint64(slot >> 128) == referenceAddress) { + } else if (uint64(slot >> 128) == liteRef) { mdStorage[0] = slot & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - } else if (uint64(slot >> 192) == referenceAddress) { + } else if (uint64(slot >> 192) == liteRef) { mdStorage[0] = slot & 0x0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - } else if (uint64(slot2) == referenceAddress) { + } else if (uint64(slot2) == liteRef) { mdStorage[0] = slot2 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000; - } else if (uint64(slot2 >> 64) == referenceAddress) { + } else if (uint64(slot2 >> 64) == liteRef) { mdStorage[0] = slot2 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF; - } else if (uint64(slot2 >> 128) == referenceAddress) { + } else if (uint64(slot2 >> 128) == liteRef) { mdStorage[0] = slot2 & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - } else if (uint64(slot2 >> 192) == referenceAddress) { + } else if (uint64(slot2 >> 192) == liteRef) { mdStorage[0] = slot2 & 0x0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; } else { revert("not assigned"); } } - function addReferenceDirect(uint256 tokenId, uint64 referenceAddress, uint256 targetMetadataId) public override { + function addReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { if (targetMetadataId != 0) { revert("Unsupported metadata ID"); } - addReference(tokenId, referenceAddress); + addReference(tokenId, liteRef); } From 3b86830410c447a0f2e38cda6c91a986b0ae0c33 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Tue, 28 Nov 2023 16:53:09 -0800 Subject: [PATCH 23/63] Payable mint support (#45) * Make unique patches global * first draft * converting test nfts to mintables * More mintable conversions * test mintables updated * Some progress * Lots of interface tweaks, mint/fee events * Refactored IPatchworkScoped interface * Non reentrancy * Coverage * proxyable mint functions --- src/IPatchwork1155Patch.sol | 10 +- src/IPatchworkAccountPatch.sol | 10 +- src/IPatchworkAssignableNFT.sol | 13 +- src/IPatchworkLiteRef.sol | 4 +- src/IPatchworkMintable.sol | 15 ++ src/IPatchworkMultiAssignableNFT.sol | 6 +- src/IPatchworkNFT.sol | 19 +- src/IPatchworkPatch.sol | 14 +- src/IPatchworkProtocol.sol | 188 ++++++++++++++-- src/IPatchworkScoped.sol | 15 ++ src/Patchwork1155Patch.sol | 2 +- src/PatchworkAccountPatch.sol | 2 +- src/PatchworkFragmentMulti.sol | 2 +- src/PatchworkFragmentSingle.sol | 2 +- src/PatchworkLiteRef.sol | 4 +- src/PatchworkPatch.sol | 2 +- src/PatchworkProtocol.sol | 259 +++++++++++++++++++++-- src/PatchworkUtils.sol | 9 + test/Fees.t.sol | 183 ++++++++++++++++ test/PatchworkFragmentMulti.t.sol | 24 +-- test/PatchworkNFT.t.sol | 158 +++++++------- test/PatchworkNFTReferences.t.sol | 4 +- test/PatchworkProtocol.t.sol | 66 +++--- test/TestDynamicArrayLiteRefNFT.t.sol | 4 +- test/nfts/TestDynamicArrayLiteRefNFT.sol | 18 +- test/nfts/TestFragmentLiteRefNFT.sol | 19 +- test/nfts/TestMultiFragmentNFT.sol | 21 +- test/nfts/TestPatchworkNFT.sol | 25 ++- 28 files changed, 874 insertions(+), 224 deletions(-) create mode 100644 src/IPatchworkMintable.sol create mode 100644 src/IPatchworkScoped.sol create mode 100644 test/Fees.t.sol diff --git a/src/IPatchwork1155Patch.sol b/src/IPatchwork1155Patch.sol index 9ffed15..c3c4597 100644 --- a/src/IPatchwork1155Patch.sol +++ b/src/IPatchwork1155Patch.sol @@ -1,18 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; +import "./IPatchworkScoped.sol"; + /** @title Patchwork Protocol 1155 Patch Interface @author Runic Labs, Inc @notice Interface for contracts supporting Patchwork patch standard */ -interface IPatchwork1155Patch { - /** - @notice Get the scope this NFT claims to belong to - @return string the name of the scope - */ - function getScopeName() external returns (string memory); - +interface IPatchwork1155Patch is IPatchworkScoped { /** @notice Creates a new token for the owner, representing a patch @param to Address of the owner of the patch token diff --git a/src/IPatchworkAccountPatch.sol b/src/IPatchworkAccountPatch.sol index e9e48e4..99a5984 100644 --- a/src/IPatchworkAccountPatch.sol +++ b/src/IPatchworkAccountPatch.sol @@ -1,18 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; +import "./IPatchworkScoped.sol"; + /** @title Patchwork Protocol Account Patch Interface @author Runic Labs, Inc @notice Interface for contracts supporting Patchwork patch standard */ -interface IPatchworkAccountPatch { - /** - @notice Get the scope this NFT claims to belong to - @return string the name of the scope - */ - function getScopeName() external returns (string memory); - +interface IPatchworkAccountPatch is IPatchworkScoped { /** @notice Creates a new token for the owner, representing a patch @param owner Address of the owner of the token diff --git a/src/IPatchworkAssignableNFT.sol b/src/IPatchworkAssignableNFT.sol index 2a1dd48..cedd0fa 100644 --- a/src/IPatchworkAssignableNFT.sol +++ b/src/IPatchworkAssignableNFT.sol @@ -1,19 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; +import "./IPatchworkScoped.sol"; + /** @title Patchwork Protocol Assignable NFT Interface @author Runic Labs, Inc @notice Interface for contracts supporting Patchwork assignment */ -interface IPatchworkAssignableNFT { - - /** - @notice Get the scope this NFT claims to belong to - @return string the name of the scope - */ - function getScopeName() external returns (string memory); - +interface IPatchworkAssignableNFT is IPatchworkScoped { /** @notice Assigns a token to another @param ourTokenId ID of our token @@ -31,5 +26,5 @@ interface IPatchworkAssignableNFT { @param by the account invoking the assignment to Patchwork Protocol @param scopeName the scope name of the contract to assign to */ - function allowAssignment(uint256 ourTokenId, address target, uint256 targetTokenId, address targetOwner, address by, string memory scopeName) external returns (bool); + function allowAssignment(uint256 ourTokenId, address target, uint256 targetTokenId, address targetOwner, address by, string memory scopeName) external view returns (bool); } \ No newline at end of file diff --git a/src/IPatchworkLiteRef.sol b/src/IPatchworkLiteRef.sol index 61b7740..11cc6e3 100644 --- a/src/IPatchworkLiteRef.sol +++ b/src/IPatchworkLiteRef.sol @@ -42,7 +42,7 @@ interface IPatchworkLiteRef { @return id ID assigned to the address @return redacted Redacted status */ - function getReferenceId(address addr) external returns (uint8 id, bool redacted); + function getReferenceId(address addr) external view returns (uint8 id, bool redacted); /** @notice Gets the address assigned to this id @@ -50,7 +50,7 @@ interface IPatchworkLiteRef { @return addr Registered address @return redacted Redacted status */ - function getReferenceAddress(uint8 id) external returns (address addr, bool redacted); + function getReferenceAddress(uint8 id) external view returns (address addr, bool redacted); /** @notice Redacts a reference address diff --git a/src/IPatchworkMintable.sol b/src/IPatchworkMintable.sol new file mode 100644 index 0000000..4a04bee --- /dev/null +++ b/src/IPatchworkMintable.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "./IPatchworkScoped.sol"; + +/** +@title Patchwork Mintable Interface +@author Runic Labs, Inc +*/ +interface IPatchworkMintable is IPatchworkScoped { + // TODO docs + function mint(address to, bytes calldata data) external payable returns (uint256 tokenId); + + function mintBatch(address to, bytes calldata data, uint256 quantity) external payable returns (uint256[] memory tokenIds); +} \ No newline at end of file diff --git a/src/IPatchworkMultiAssignableNFT.sol b/src/IPatchworkMultiAssignableNFT.sol index 6b60d5f..fe4ee3b 100644 --- a/src/IPatchworkMultiAssignableNFT.sol +++ b/src/IPatchworkMultiAssignableNFT.sol @@ -21,7 +21,7 @@ interface IPatchworkMultiAssignableNFT is IPatchworkAssignableNFT { @param target the address of the target @param targetTokenId the tokenId of the target */ - function isAssignedTo(uint256 ourTokenId, address target, uint256 targetTokenId) external returns (bool); + function isAssignedTo(uint256 ourTokenId, address target, uint256 targetTokenId) external view returns (bool); /** @notice Unassigns a token @@ -33,7 +33,7 @@ interface IPatchworkMultiAssignableNFT is IPatchworkAssignableNFT { @notice Counts the number of unique assignments this token has @param tokenId tokenId of our fragment */ - function getAssignmentCount(uint256 tokenId) external returns (uint256); + function getAssignmentCount(uint256 tokenId) external view returns (uint256); /** @notice Gets assignments for a fragment @@ -41,5 +41,5 @@ interface IPatchworkMultiAssignableNFT is IPatchworkAssignableNFT { @param offset the page offset @param count the maximum numer of entries to return */ - function getAssignments(uint256 tokenId, uint256 offset, uint256 count) external returns (Assignment[] memory); + function getAssignments(uint256 tokenId, uint256 offset, uint256 count) external view returns (Assignment[] memory); } \ No newline at end of file diff --git a/src/IPatchworkNFT.sol b/src/IPatchworkNFT.sol index 0c9567c..8937d9f 100644 --- a/src/IPatchworkNFT.sol +++ b/src/IPatchworkNFT.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import "./IERC5192.sol"; +import "./IPatchworkScoped.sol"; /** @title Patchwork Protocol NFT Interface Metadata @@ -75,7 +76,7 @@ interface PatchworkNFTInterfaceMeta { @author Runic Labs, Inc @notice Interface for contracts supporting Patchwork metadata standard */ -interface IPatchworkNFT is PatchworkNFTInterfaceMeta, IERC5192 { +interface IPatchworkNFT is IPatchworkScoped, PatchworkNFTInterfaceMeta, IERC5192 { /** @notice Emitted when the freeze status is changed to frozen. @param tokenId The identifier for a token. @@ -101,30 +102,24 @@ interface IPatchworkNFT is PatchworkNFTInterfaceMeta, IERC5192 { */ event SchemaChange(address indexed addr); - /** - @notice Get the scope this NFT claims to belong to - @return string the name of the scope - */ - function getScopeName() external returns (string memory); - /** @notice Returns the URI of the schema @return string the URI of the schema */ - function schemaURI() external returns (string memory); + function schemaURI() external view returns (string memory); /** @notice Returns the metadata schema @return MetadataSchema the metadata schema */ - function schema() external returns (MetadataSchema memory); + function schema() external view returns (MetadataSchema memory); /** @notice Returns the URI of the image associated with the given token ID @param tokenId ID of the token @return string the image URI */ - function imageURI(uint256 tokenId) external returns (string memory); + function imageURI(uint256 tokenId) external view returns (string memory); /** @notice Sets permissions for a given address @@ -147,14 +142,14 @@ interface IPatchworkNFT is PatchworkNFTInterfaceMeta, IERC5192 { @param slot Slot to load metadata from @return uint256 the raw slot data as a uint256 */ - function loadPackedMetadataSlot(uint256 tokenId, uint256 slot) external returns (uint256); + function loadPackedMetadataSlot(uint256 tokenId, uint256 slot) external view returns (uint256); /** @notice Returns the freeze nonce for a given token ID @param tokenId ID of the token @return nonce the nonce */ - function getFreezeNonce(uint256 tokenId) external returns (uint256 nonce); + function getFreezeNonce(uint256 tokenId) external view returns (uint256 nonce); /** @notice Sets the freeze status of a token diff --git a/src/IPatchworkPatch.sol b/src/IPatchworkPatch.sol index 69ddb8e..396a383 100644 --- a/src/IPatchworkPatch.sol +++ b/src/IPatchworkPatch.sol @@ -1,18 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; +import "./IPatchworkScoped.sol"; + /** @title Patchwork Protocol Patch Interface @author Runic Labs, Inc @notice Interface for contracts supporting Patchwork patch standard */ -interface IPatchworkPatch { - /** - @notice Get the scope this NFT claims to belong to - @return string the name of the scope - */ - function getScopeName() external returns (string memory); - +interface IPatchworkPatch is IPatchworkScoped { /** @notice Creates a new token for the owner, representing a patch @param owner Address of the owner of the token @@ -33,7 +29,7 @@ interface IPatchworkPatch { @param tokenId ID of the token @return address Address of the owner */ - function unpatchedOwnerOf(uint256 tokenId) external returns (address); + function unpatchedOwnerOf(uint256 tokenId) external view returns (address); /** @notice Returns the token ID (if it exists) for an NFT that may have been patched @@ -42,5 +38,5 @@ interface IPatchworkPatch { @param originalNFTTokenId ID of the original NFT token @return tokenId ID of the newly minted token */ - function getTokenIdForOriginalNFT(address originalNFTAddress, uint256 originalNFTTokenId) external returns (uint256 tokenId); + function getTokenIdForOriginalNFT(address originalNFTAddress, uint256 originalNFTTokenId) external view returns (uint256 tokenId); } \ No newline at end of file diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index 92faf13..e52bb3c 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -210,14 +210,51 @@ interface IPatchworkProtocol { error DataIntegrityError(address addr, uint256 tokenId, address addr2, uint256 tokenId2); /** - @notice The operation is not supported + @notice The available balance does not satisfy the amount */ - error UnsupportedOperation(); + error InsufficientFunds(); + + /** + @notice The supplied fee is not the corret amount + */ + error IncorrectFeeAmount(); + /** + @notice Minting is not active for this address + */ + error MintNotActive(); + + /** + @notice The value could not be sent + */ + error FailedToSend(); + /** @notice The contract is not supported */ error UnsupportedContract(); + + /** + @notice The operation is not supported + */ + error UnsupportedOperation(); + + /** + @notice Protocol Fee Configuration + */ + struct ProtocolFeeConfig { + uint256 mintBp; /// mint basis points (10000 = 100%) + uint256 patchBp; /// patch basis points (10000 = 100%) + uint256 assignBp; /// assign basis points (10000 = 100%) + } + + /** + @notice Mint configuration + */ + struct MintConfig { + uint256 flatFee; /// wei + bool active; /// If the mint is active + } /** @notice Represents a defined scope within the system @@ -266,13 +303,54 @@ interface IPatchworkProtocol { */ mapping(address => bool) whitelist; - /** - @notice Mapped list of unique patches associated with this scope - @dev Hash of the patch mapped to a boolean indicating its uniqueness - */ - mapping(bytes32 => bool) uniquePatches; + mapping(address => MintConfig) mintConfigurations; + + mapping(address => uint256) patchFees; + + mapping(address => uint256) assignFees; + + uint256 balance; + + mapping(address => bool) bankers; } + function setMintConfiguration(address addr, MintConfig memory config) external; + + function getMintConfiguration(address addr) external view returns (MintConfig memory config); + + function setPatchFee(address addr, uint256 baseFee) external; + + function getPatchFee(address addr) external view returns (uint256 baseFee); + + function setAssignFee(address fragmentAddress, uint256 baseFee) external; + + function getAssignFee(address fragmentAddress) external view returns (uint256 baseFee); + + function addBanker(string memory scopeName, address addr) external; + + function removeBanker(string memory scopeName, address addr) external; + + // TODO nonreentrant + function withdraw(string memory scopeName, uint256 amount) external; + + function balanceOf(string memory scopeName) external view returns (uint256 balance); + + function mint(address to, address nft, bytes calldata data) external payable returns (uint256 tokenId); + + function mintBatch(address to, address nft, bytes calldata data, uint256 quantity) external payable returns (uint256[] memory tokenIds); + + function setProtocolFeeConfig(ProtocolFeeConfig memory config) external; + + function getProtocolFeeConfig() external view returns (ProtocolFeeConfig memory config); + + function addProtocolBanker(address addr) external; + + function removeProtocolBanker(address addr) external; + + function withdrawFromProtocol(uint256 balance) external; + + function balanceOfProtocol() external view returns (uint256 balance); + /** @notice Emitted when a fragment is assigned @param owner The owner of the target and fragment @@ -397,6 +475,80 @@ interface IPatchworkProtocol { */ event ScopeWhitelistRemove(string scopeName, address indexed actor, address indexed addr); + /** + @notice Emitted when a mint is configured + @param scopeName The name of the scope + @param nft The address of the NFT that is mintable + @param config The mint configuration + */ + event MintConfigure(string scopeName, address indexed actor, address indexed nft, MintConfig config); + + /** + @notice Emitted when a banker is added to a scope + @param scopeName The name of the scope + @param actor The address responsible for the action + @param banker The banker that was added + */ + event ScopeBankerAdd(string scopeName, address indexed actor, address indexed banker); + + /** + @notice Emitted when a banker is removed from a scope + @param scopeName The name of the scope + @param actor The address responsible for the action + @param banker The banker that was removed + */ + event ScopeBankerRemove(string scopeName, address indexed actor, address indexed banker); + + /** + @notice Emitted when a withdrawl is made from a scope + @param scopeName The name of the scope + @param actor The address responsible for the action + @param amount The amount withdrawn + */ + event ScopeWithdraw(string scopeName, address indexed actor, uint256 amount); + + /** + @notice Emitted when a banker is added to the protocol + @param actor The address responsible for the action + @param banker The banker that was added + */ + event ProtocolBankerAdd(address indexed actor, address indexed banker); + + /** + @notice Emitted when a banker is removed from the protocol + @param actor The address responsible for the action + @param banker The banker that was removed + */ + event ProtocolBankerRemove(address indexed actor, address indexed banker); + + /** + @notice Emitted when a withdrawl is made from the protocol + @param actor The address responsible for the action + @param amount The amount withdrawn + */ + event ProtocolWithdraw(address indexed actor, uint256 amount); + + /** + @notice Emitted on mint + @param actor The address responsible for the action + @param scopeName The scope of the NFT + @param to The receipient of the mint + @param nft The nft minted + @param data The data used to mint + */ + event Mint(address indexed actor, string scopeName, address indexed to, address indexed nft, bytes data); + + /** + @notice Emitted on batch mint + @param actor The address responsible for the action + @param scopeName The scope of the NFT + @param to The receipient of the mint + @param nft The nft minted + @param data The data used to mint + @param quantity The quantity minted + */ + event MintBatch(address indexed actor, string scopeName, address indexed to, address indexed nft, bytes data, uint256 quantity); + /** @notice Claim a scope @param scopeName the name of the scope @@ -428,14 +580,14 @@ interface IPatchworkProtocol { @param scopeName Name of the scope @return ownerElect Address of the scope's owner-elect */ - function getScopeOwnerElect(string calldata scopeName) external returns (address ownerElect); + function getScopeOwnerElect(string calldata scopeName) external view returns (address ownerElect); /** @notice Get owner of a scope @param scopeName Name of the scope @return owner Address of the scope owner */ - function getScopeOwner(string calldata scopeName) external returns (address owner); + function getScopeOwner(string calldata scopeName) external view returns (address owner); /** @notice Add an operator to a scope @@ -482,7 +634,11 @@ interface IPatchworkProtocol { @param patchAddress Address of the IPatchworkPatch to mint @return tokenId Token ID of the newly created patch */ - function createPatch(address owner, address originalNFTAddress, uint originalNFTTokenId, address patchAddress) external returns (uint256 tokenId); + function createPatch(address owner, address originalNFTAddress, uint originalNFTTokenId, address patchAddress) external payable returns (uint256 tokenId); + + // patch, patch1155, patchAccount + // assign, assign, assignBatch, assignBatch (also rename in base NFTs?) + // mint, mintBatch /** @notice Create a new 1155 patch @@ -492,7 +648,7 @@ interface IPatchworkProtocol { @param patchAddress Address of the IPatchworkPatch to mint @return tokenId Token ID of the newly created patch */ - function create1155Patch(address to, address originalNFTAddress, uint originalNFTTokenId, address originalAccount, address patchAddress) external returns (uint256 tokenId); + function create1155Patch(address to, address originalNFTAddress, uint originalNFTTokenId, address originalAccount, address patchAddress) external payable returns (uint256 tokenId); /** @notice Create a new account patch @@ -501,7 +657,7 @@ interface IPatchworkProtocol { @param patchAddress Address of the IPatchworkPatch to mint @return tokenId Token ID of the newly created patch */ - function createAccountPatch(address owner, address originalAddress, address patchAddress) external returns (uint256 tokenId); + function createAccountPatch(address owner, address originalAddress, address patchAddress) external payable returns (uint256 tokenId); /** @notice Assigns an NFT relation to have an IPatchworkLiteRef form a LiteRef to a IPatchworkAssignableNFT @@ -510,7 +666,7 @@ interface IPatchworkProtocol { @param target The IPatchworkLiteRef address to hold the reference to the fragment @param targetTokenId The IPatchworkLiteRef Token ID to hold the reference to the fragment */ - function assignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external; + function assignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external payable; /** @notice Assigns an NFT relation to have an IPatchworkLiteRef form a LiteRef to a IPatchworkAssignableNFT @@ -520,7 +676,7 @@ interface IPatchworkProtocol { @param targetTokenId The IPatchworkLiteRef Token ID to hold the reference to the fragment @param targetMetadataId The metadata ID on the target NFT to store the reference in */ - function assignNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) external; + function assignNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) external payable; /** @notice Assign multiple NFT fragments to a target NFT in batch @@ -529,7 +685,7 @@ interface IPatchworkProtocol { @param target The address of the target IPatchworkLiteRef NFT @param targetTokenId The token ID of the target IPatchworkLiteRef NFT */ - function batchAssignNFT(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) external; + function batchAssignNFT(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) external payable; /** @notice Assign multiple NFT fragments to a target NFT in batch @@ -539,7 +695,7 @@ interface IPatchworkProtocol { @param targetTokenId The token ID of the target IPatchworkLiteRef NFT @param targetMetadataId The metadata ID on the target NFT to store the references in */ - function batchAssignNFTDirect(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId, uint256 targetMetadataId) external; + function batchAssignNFTDirect(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId, uint256 targetMetadataId) external payable; /** @notice Unassign a NFT fragment from a target NFT diff --git a/src/IPatchworkScoped.sol b/src/IPatchworkScoped.sol new file mode 100644 index 0000000..e1af777 --- /dev/null +++ b/src/IPatchworkScoped.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/** +@title Patchwork Protocol Scoped Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting scopes +*/ +interface IPatchworkScoped { + /** + @notice Get the scope this NFT claims to belong to + @return string the name of the scope + */ + function getScopeName() external view returns (string memory); +} \ No newline at end of file diff --git a/src/Patchwork1155Patch.sol b/src/Patchwork1155Patch.sol index 56ee210..111d735 100644 --- a/src/Patchwork1155Patch.sol +++ b/src/Patchwork1155Patch.sol @@ -34,7 +34,7 @@ abstract contract Patchwork1155Patch is PatchworkNFT, IPatchwork1155Patch { /** @dev See {IPatchworkNFT-getScopeName} */ - function getScopeName() public view virtual override(PatchworkNFT, IPatchwork1155Patch) returns (string memory) { + function getScopeName() public view virtual override(PatchworkNFT, IPatchworkScoped) returns (string memory) { return _scopeName; } diff --git a/src/PatchworkAccountPatch.sol b/src/PatchworkAccountPatch.sol index 4ffef1a..a53b08b 100644 --- a/src/PatchworkAccountPatch.sol +++ b/src/PatchworkAccountPatch.sol @@ -29,7 +29,7 @@ abstract contract PatchworkAccountPatch is PatchworkNFT, IPatchworkAccountPatch /** @dev See {IPatchworkNFT-getScopeName} */ - function getScopeName() public view virtual override(PatchworkNFT, IPatchworkAccountPatch) returns (string memory) { + function getScopeName() public view virtual override(PatchworkNFT, IPatchworkScoped) returns (string memory) { return _scopeName; } diff --git a/src/PatchworkFragmentMulti.sol b/src/PatchworkFragmentMulti.sol index 9f60c33..5f42787 100644 --- a/src/PatchworkFragmentMulti.sol +++ b/src/PatchworkFragmentMulti.sol @@ -23,7 +23,7 @@ abstract contract PatchworkFragmentMulti is PatchworkNFT, IPatchworkMultiAssigna /** @dev See {IPatchworkNFT-getScopeName} */ - function getScopeName() public view virtual override (IPatchworkAssignableNFT, PatchworkNFT) returns (string memory) { + function getScopeName() public view virtual override (PatchworkNFT, IPatchworkScoped) returns (string memory) { return _scopeName; } diff --git a/src/PatchworkFragmentSingle.sol b/src/PatchworkFragmentSingle.sol index d6b114f..bacd023 100644 --- a/src/PatchworkFragmentSingle.sol +++ b/src/PatchworkFragmentSingle.sol @@ -25,7 +25,7 @@ abstract contract PatchworkFragmentSingle is PatchworkNFT, IPatchworkSingleAssig /** @dev See {IPatchworkNFT-getScopeName} */ - function getScopeName() public view virtual override (IPatchworkAssignableNFT, PatchworkNFT) returns (string memory) { + function getScopeName() public view virtual override (PatchworkNFT, IPatchworkScoped) returns (string memory) { return _scopeName; } diff --git a/src/PatchworkLiteRef.sol b/src/PatchworkLiteRef.sol index 07c42f5..4bf8734 100644 --- a/src/PatchworkLiteRef.sol +++ b/src/PatchworkLiteRef.sol @@ -65,7 +65,7 @@ abstract contract PatchworkLiteRef is IPatchworkLiteRef, ERC165 { /** @dev See {IPatchworkLiteRef-getReferenceId} */ - function getReferenceId(address addr) public virtual returns (uint8 id, bool redacted) { + function getReferenceId(address addr) public virtual view returns (uint8 id, bool redacted) { uint8 refId = _referenceAddressIds[addr]; return (refId, _redactedReferenceIds[refId]); } @@ -73,7 +73,7 @@ abstract contract PatchworkLiteRef is IPatchworkLiteRef, ERC165 { /** @dev See {IPatchworkLiteRef-getReferenceAddress} */ - function getReferenceAddress(uint8 id) public virtual returns (address addr, bool redacted) { + function getReferenceAddress(uint8 id) public virtual view returns (address addr, bool redacted) { return (_referenceAddresses[id], _redactedReferenceIds[id]); } diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index d59a2b0..4d960cd 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -32,7 +32,7 @@ abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { /** @dev See {IPatchworkNFT-getScopeName} */ - function getScopeName() public view virtual override(PatchworkNFT, IPatchworkPatch) returns (string memory) { + function getScopeName() public view virtual override(PatchworkNFT, IPatchworkScoped) returns (string memory) { return _scopeName; } diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 7d30a39..ee1b276 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -9,14 +9,18 @@ import "./IPatchworkPatch.sol"; import "./IPatchwork1155Patch.sol"; import "./IPatchworkAccountPatch.sol"; import "./IPatchworkProtocol.sol"; +import "./IPatchworkMintable.sol"; +import "./IPatchworkScoped.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; /** @title Patchwork Protocol @author Runic Labs, Inc @notice Manages data integrity of relational NFTs implemented with Patchwork interfaces */ -contract PatchworkProtocol is IPatchworkProtocol { +contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { /// Scopes mapping(string => Scope) private _scopes; @@ -27,6 +31,26 @@ contract PatchworkProtocol is IPatchworkProtocol { */ mapping(bytes32 => bool) _liteRefs; + /** + @notice unique patches + @dev Hash of the patch mapped to a boolean indicating its uniqueness + */ + mapping(bytes32 => bool) _uniquePatches; + + uint256 _protocolBalance; + + mapping(address => bool) _protocolBankers; + + ProtocolFeeConfig _protocolFeeConfig; + + // TODO overrides? + mapping(string => ProtocolFeeConfig) _scopeFeeOverrides; // scope-based fee overrides + mapping(address => uint256) _addressBpOverride; + + uint256 public constant TRANSFER_GAS_LIMIT = 5000; + + constructor() Ownable() ReentrancyGuard() {} + /** @dev See {IPatchworkProtocol-claimScope} */ @@ -124,6 +148,185 @@ contract PatchworkProtocol is IPatchworkProtocol { emit ScopeRuleChange(scopeName, msg.sender, allowUserPatch, allowUserAssign, requireWhitelist); } + function setMintConfiguration(address addr, MintConfig memory config) public { + if (!IERC165(addr).supportsInterface(type(IPatchworkMintable).interfaceId)) { + revert UnsupportedContract(); + } + IPatchworkMintable mintable = IPatchworkMintable(addr); + string memory scopeName = mintable.getScopeName(); + Scope storage scope = _mustHaveScope(scopeName); + _mustBeWhitelisted(scopeName, scope, addr); + _mustBeOwnerOrOperator(scope); + scope.mintConfigurations[addr] = config; + emit MintConfigure(scopeName, msg.sender, addr, config); + } + + function getMintConfiguration(address addr) public view returns (MintConfig memory config) { + if (!IERC165(addr).supportsInterface(type(IPatchworkMintable).interfaceId)) { + revert UnsupportedContract(); + } + Scope storage scope = _mustHaveScope(IPatchworkMintable(addr).getScopeName()); + return scope.mintConfigurations[addr]; + } + + function setPatchFee(address addr, uint256 baseFee) public { + if (!IERC165(addr).supportsInterface(type(IPatchworkScoped).interfaceId)) { + revert UnsupportedContract(); + } + string memory scopeName = IPatchworkScoped(addr).getScopeName(); + Scope storage scope = _mustHaveScope(scopeName); + _mustBeWhitelisted(scopeName, scope, addr); + _mustBeOwnerOrOperator(scope); + scope.patchFees[addr] = baseFee; + } + + function getPatchFee(address addr) public view returns (uint256 baseFee) { + if (!IERC165(addr).supportsInterface(type(IPatchworkScoped).interfaceId)) { + revert UnsupportedContract(); + } + Scope storage scope = _mustHaveScope(IPatchworkScoped(addr).getScopeName()); + return scope.patchFees[addr]; + } + + function setAssignFee(address fragmentAddress, uint256 baseFee) public { + if (!IERC165(fragmentAddress).supportsInterface(type(IPatchworkScoped).interfaceId)) { + revert UnsupportedContract(); + } + string memory scopeName = IPatchworkScoped(fragmentAddress).getScopeName(); + Scope storage scope = _mustHaveScope(scopeName); + _mustBeWhitelisted(scopeName, scope, fragmentAddress); + _mustBeOwnerOrOperator(scope); + scope.assignFees[fragmentAddress] = baseFee; + } + + function getAssignFee(address fragmentAddress) public view returns (uint256 baseFee) { + if (!IERC165(fragmentAddress).supportsInterface(type(IPatchworkScoped).interfaceId)) { + revert UnsupportedContract(); + } + Scope storage scope = _mustHaveScope(IPatchworkScoped(fragmentAddress).getScopeName()); + return scope.assignFees[fragmentAddress]; + } + + function addBanker(string memory scopeName, address addr) public { + Scope storage scope = _mustHaveScope(scopeName); + _mustBeOwnerOrOperator(scope); + scope.bankers[addr] = true; + emit ScopeBankerAdd(scopeName, msg.sender, addr); + } + + function removeBanker(string memory scopeName, address addr) public { + Scope storage scope = _mustHaveScope(scopeName); + _mustBeOwnerOrOperator(scope); + delete scope.bankers[addr]; + emit ScopeBankerRemove(scopeName, msg.sender, addr); + } + + function withdraw(string memory scopeName, uint256 amount) public nonReentrant { + Scope storage scope = _mustHaveScope(scopeName); + if (msg.sender != scope.owner && !scope.bankers[msg.sender]) { + revert NotAuthorized(msg.sender); + } + if (amount > scope.balance) { + revert InsufficientFunds(); + } + // modify state before calling to send + scope.balance -= amount; + // transfer funds + (bool sent,) = msg.sender.call{value: amount, gas: TRANSFER_GAS_LIMIT}(""); + if (!sent) { + revert FailedToSend(); + } + emit ScopeWithdraw(scopeName, msg.sender, amount); + } + + function balanceOf(string memory scopeName) public view returns (uint256 balance) { + Scope storage scope = _mustHaveScope(scopeName); + return scope.balance; + } + + function mint(address to, address nft, bytes calldata data) external payable returns (uint256 tokenId) { + (MintConfig memory config, string memory scopeName, Scope storage scope) = _setupMint(nft); + if (msg.value != config.flatFee) { + revert IncorrectFeeAmount(); + } + _handleMintFee(scope); + tokenId = IPatchworkMintable(nft).mint(to, data); + emit Mint(msg.sender, scopeName, to, nft, data); + } + + function mintBatch(address to, address nft, bytes calldata data, uint256 quantity) external payable returns (uint256[] memory tokenIds) { + (MintConfig memory config, string memory scopeName, Scope storage scope) = _setupMint(nft); + uint256 totalFee = config.flatFee * quantity; + if (msg.value != totalFee) { + revert IncorrectFeeAmount(); + } + _handleMintFee(scope); + tokenIds = IPatchworkMintable(nft).mintBatch(to, data, quantity); + emit MintBatch(msg.sender, scopeName, to, nft, data, quantity); + } + + function _setupMint(address nft) internal view returns (MintConfig memory config, string memory scopeName, Scope storage scope) { + if (!IERC165(nft).supportsInterface(type(IPatchworkMintable).interfaceId)) { + revert UnsupportedContract(); + } + scopeName = IPatchworkMintable(nft).getScopeName(); + scope = _mustHaveScope(scopeName); + _mustBeWhitelisted(scopeName, scope, nft); + config = scope.mintConfigurations[nft]; + if (!config.active) { + revert MintNotActive(); + } + } + + function _handleMintFee(Scope storage scope) internal { + // Account for 100% of the message value + if (msg.value != 0) { + uint256 protocolFee = msg.value * _protocolFeeConfig.mintBp / 10000; + _protocolBalance += protocolFee; + scope.balance += msg.value - protocolFee; + } + } + + function setProtocolFeeConfig(ProtocolFeeConfig memory config) public { + if (msg.sender != owner() && _protocolBankers[msg.sender] == false) { + revert NotAuthorized(msg.sender); + } + _protocolFeeConfig = config; + } + + function getProtocolFeeConfig() public view returns (ProtocolFeeConfig memory config) { + return _protocolFeeConfig; + } + + function addProtocolBanker(address addr) external onlyOwner { + _protocolBankers[addr] = true; + emit ProtocolBankerAdd(msg.sender, addr); + } + + function removeProtocolBanker(address addr) external onlyOwner { + delete _protocolBankers[addr]; + emit ProtocolBankerRemove(msg.sender, addr); + } + + function withdrawFromProtocol(uint256 amount) external nonReentrant { + if (msg.sender != owner() && _protocolBankers[msg.sender] == false) { + revert NotAuthorized(msg.sender); + } + if (amount > _protocolBalance) { + revert InsufficientFunds(); + } + _protocolBalance -= amount; + (bool sent,) = msg.sender.call{value: amount, gas: TRANSFER_GAS_LIMIT}(""); + if (!sent) { + revert FailedToSend(); + } + emit ProtocolWithdraw(msg.sender, amount); + } + + function balanceOfProtocol() public view returns (uint256 balance) { + return _protocolBalance; + } + /** @dev See {IPatchworkProtocol-addWhitelist} */ @@ -147,7 +350,7 @@ contract PatchworkProtocol is IPatchworkProtocol { /** @dev See {IPatchworkProtocol-createPatch} */ - function createPatch(address owner, address originalNFTAddress, uint originalNFTTokenId, address patchAddress) public returns (uint256 tokenId) { + function createPatch(address owner, address originalNFTAddress, uint originalNFTTokenId, address patchAddress) external payable returns (uint256 tokenId) { IPatchworkPatch patch = IPatchworkPatch(patchAddress); string memory scopeName = patch.getScopeName(); // mint a Patch that is soulbound to the originalNFT using the contract address at patchAddress which must support Patchwork metadata @@ -160,12 +363,13 @@ contract PatchworkProtocol is IPatchworkProtocol { } else { revert NotAuthorized(msg.sender); } + _handlePatchFee(scope, patchAddress); // limit this to one unique patch (originalNFTAddress+TokenID+patchAddress) bytes32 _hash = keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId, patchAddress)); - if (scope.uniquePatches[_hash]) { + if (_uniquePatches[_hash]) { revert AlreadyPatched(originalNFTAddress, originalNFTTokenId, patchAddress); } - scope.uniquePatches[_hash] = true; + _uniquePatches[_hash] = true; tokenId = patch.mintPatch(owner, originalNFTAddress, originalNFTTokenId); emit Patch(owner, originalNFTAddress, originalNFTTokenId, patchAddress, tokenId); return tokenId; @@ -174,7 +378,7 @@ contract PatchworkProtocol is IPatchworkProtocol { /** @dev See {IPatchworkProtocol-create1155Patch} */ - function create1155Patch(address to, address originalNFTAddress, uint originalNFTTokenId, address originalAccount, address patchAddress) public returns (uint256 tokenId) { + function create1155Patch(address to, address originalNFTAddress, uint originalNFTTokenId, address originalAccount, address patchAddress) external payable returns (uint256 tokenId) { IPatchwork1155Patch patch = IPatchwork1155Patch(patchAddress); string memory scopeName = patch.getScopeName(); // mint a Patch that is soulbound to the originalNFT using the contract address at patchAddress which must support Patchwork metadata @@ -187,12 +391,13 @@ contract PatchworkProtocol is IPatchworkProtocol { } else { revert NotAuthorized(msg.sender); } + _handlePatchFee(scope, patchAddress); // limit this to one unique patch (originalNFTAddress+TokenID+patchAddress) bytes32 _hash = keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId, originalAccount, patchAddress)); - if (scope.uniquePatches[_hash]) { + if (_uniquePatches[_hash]) { revert ERC1155AlreadyPatched(originalNFTAddress, originalNFTTokenId, originalAccount, patchAddress); } - scope.uniquePatches[_hash] = true; + _uniquePatches[_hash] = true; tokenId = patch.mintPatch(to, originalNFTAddress, originalNFTTokenId, originalAccount); emit ERC1155Patch(to, originalNFTAddress, originalNFTTokenId, originalAccount, patchAddress, tokenId); return tokenId; @@ -200,7 +405,7 @@ contract PatchworkProtocol is IPatchworkProtocol { /** @dev See {IPatchworkProtocol-createAccountPatch} */ - function createAccountPatch(address owner, address originalAddress, address patchAddress) public returns (uint256 tokenId) { + function createAccountPatch(address owner, address originalAddress, address patchAddress) external payable returns (uint256 tokenId) { IPatchworkAccountPatch patch = IPatchworkAccountPatch(patchAddress); string memory scopeName = patch.getScopeName(); // mint a Patch that is soulbound to the originalNFT using the contract address at patchAddress which must support Patchwork metadata @@ -213,21 +418,46 @@ contract PatchworkProtocol is IPatchworkProtocol { } else { revert NotAuthorized(msg.sender); } + _handlePatchFee(scope, patchAddress); // limit this to one unique patch (originalAddress+TokenID+patchAddress) bytes32 _hash = keccak256(abi.encodePacked(originalAddress, patchAddress)); - if (scope.uniquePatches[_hash]) { + if (_uniquePatches[_hash]) { revert AccountAlreadyPatched(originalAddress, patchAddress); } - scope.uniquePatches[_hash] = true; + _uniquePatches[_hash] = true; tokenId = patch.mintPatch(owner, originalAddress); emit AccountPatch(owner, originalAddress, patchAddress, tokenId); return tokenId; } + function _handlePatchFee(Scope storage scope, address patchAddress) private { + uint256 patchFee = scope.patchFees[patchAddress]; + if (msg.value != patchFee) { + revert IncorrectFeeAmount(); + } + if (msg.value > 0) { + uint256 protocolFee = msg.value * _protocolFeeConfig.patchBp / 10000; + _protocolBalance += protocolFee; + scope.balance += msg.value - protocolFee; + } + } + + function _handleAssignFee(Scope storage scope, address fragmentAddress) private { + uint256 assignFee = scope.assignFees[fragmentAddress]; + if (msg.value != assignFee) { + revert IncorrectFeeAmount(); + } + if (msg.value > 0) { + uint256 protocolFee = msg.value * _protocolFeeConfig.assignBp / 10000; + _protocolBalance += protocolFee; + scope.balance += msg.value - protocolFee; + } + } + /** @dev See {IPatchworkProtocol-assignNFT} */ - function assignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { + function assignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public payable mustNotBeFrozen(target, targetTokenId) { address targetOwner = IERC721(target).ownerOf(targetTokenId); uint64 ref = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); IPatchworkLiteRef(target).addReference(targetTokenId, ref); @@ -236,7 +466,7 @@ contract PatchworkProtocol is IPatchworkProtocol { /** @dev See {IPatchworkProtocol-assignNFTDirect} */ - function assignNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { + function assignNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public payable mustNotBeFrozen(target, targetTokenId) { address targetOwner = IERC721(target).ownerOf(targetTokenId); uint64 ref = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); IPatchworkLiteRef(target).addReferenceDirect(targetTokenId, ref, targetMetadataId); @@ -245,7 +475,7 @@ contract PatchworkProtocol is IPatchworkProtocol { /** @dev See {IPatchworkProtocol-batchAssignNFT} */ - function batchAssignNFT(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) public mustNotBeFrozen(target, targetTokenId) { + function batchAssignNFT(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) public payable mustNotBeFrozen(target, targetTokenId) { (uint64[] memory refs, ) = _batchAssignCommon(fragments, tokenIds, target, targetTokenId); IPatchworkLiteRef(target).batchAddReferences(targetTokenId, refs); } @@ -253,7 +483,7 @@ contract PatchworkProtocol is IPatchworkProtocol { /** @dev See {IPatchworkProtocol-batchAssignNFTDirect} */ - function batchAssignNFTDirect(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { + function batchAssignNFTDirect(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId, uint256 targetMetadataId) public payable mustNotBeFrozen(target, targetTokenId) { (uint64[] memory refs, ) = _batchAssignCommon(fragments, tokenIds, target, targetTokenId); IPatchworkLiteRef(target).batchAddReferencesDirect(targetTokenId, refs, targetMetadataId); } @@ -300,6 +530,7 @@ contract PatchworkProtocol is IPatchworkProtocol { string memory fragmentScopeName = assignableNFT.getScopeName(); Scope storage fragmentScope = _mustHaveScope(fragmentScopeName); _mustBeWhitelisted(fragmentScopeName, fragmentScope, fragment); + _handleAssignFee(fragmentScope, fragment); } if (targetScope.owner == msg.sender || targetScope.operators[msg.sender]) { // all good diff --git a/src/PatchworkUtils.sol b/src/PatchworkUtils.sol index 0a0c8bd..9aabc60 100644 --- a/src/PatchworkUtils.sol +++ b/src/PatchworkUtils.sol @@ -67,4 +67,13 @@ library PatchworkUtils { } out = string(trimmedByteArray); } + + function convertUint16ToBytes(uint16 input) public pure returns (bytes memory) { + // Extract the higher and lower bytes + bytes1 high = bytes1(uint8(input >> 8)); + bytes1 low = bytes1(uint8(input & 0xFF)); + + // Return the two bytes as a dynamic bytes array + return abi.encodePacked(high, low); + } } \ No newline at end of file diff --git a/test/Fees.t.sol b/test/Fees.t.sol new file mode 100644 index 0000000..7af12c6 --- /dev/null +++ b/test/Fees.t.sol @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "../src/PatchworkProtocol.sol"; +import "./nfts/Test1155PatchNFT.sol"; +import "./nfts/TestBase1155.sol"; +import "./nfts/TestFragmentLiteRefNFT.sol"; +import "./nfts/TestDynamicArrayLiteRefNFT.sol"; +import "./nfts/TestMultiFragmentNFT.sol"; +import "./nfts/TestPatchLiteRefNFT.sol"; +import "./nfts/TestAccountPatchNFT.sol"; + +contract FeesTest is Test { + + PatchworkProtocol _prot; + + string _scopeName; + address _defaultUser; + address _scopeOwner; + address _patchworkOwner; + address _userAddress; + address _user2Address; + + function setUp() public { + _defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; + _patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; + _userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; + _user2Address = address(550001); + _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; + + vm.prank(_patchworkOwner); + _prot = new PatchworkProtocol(); + vm.prank(_patchworkOwner); + _prot.setProtocolFeeConfig(IPatchworkProtocol.ProtocolFeeConfig(1000, 1000, 1000)); // 10%, 10%, 10% + + vm.startPrank(_scopeOwner); + _scopeName = "testscope"; + _prot.claimScope(_scopeName); + _prot.setScopeRules(_scopeName, false, false, false); + vm.stopPrank(); + } + + function testProtocolBankers() public { + vm.expectRevert("Ownable: caller is not the owner"); // caller is not owner + _prot.addProtocolBanker(_defaultUser); + vm.prank(_patchworkOwner); + _prot.addProtocolBanker(_user2Address); + vm.startPrank(_scopeOwner); + TestFragmentLiteRefNFT lr = new TestFragmentLiteRefNFT(address(_prot)); + _prot.addWhitelist(_scopeName, address(lr)); + _prot.setMintConfiguration(address(lr), IPatchworkProtocol.MintConfig(1000000000, true)); + vm.stopPrank(); + // mint something just to get some money in the account + IPatchworkProtocol.MintConfig memory mc = _prot.getMintConfiguration(address(lr)); + uint256 mintCost = mc.flatFee; + assertEq(1000000000, mintCost); + _prot.mint{value: mintCost}(_userAddress, address(lr), ""); + assertEq(900000000, _prot.balanceOf(_scopeName)); + assertEq(100000000, _prot.balanceOfProtocol()); + // default user not authorized + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _prot.withdrawFromProtocol(100000000); + vm.prank(_user2Address); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InsufficientFunds.selector)); + _prot.withdrawFromProtocol(500000000); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); + _prot.withdrawFromProtocol(50000000); + // banker + owner should work + vm.prank(_user2Address); + _prot.withdrawFromProtocol(50000000); + // Remove a banker + vm.expectRevert("Ownable: caller is not the owner"); + _prot.removeProtocolBanker(_user2Address); + vm.prank(_patchworkOwner); + _prot.removeProtocolBanker(_user2Address); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + vm.prank(_user2Address); + _prot.withdrawFromProtocol(50000000); + vm.prank(_patchworkOwner); + _prot.withdrawFromProtocol(50000000); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InsufficientFunds.selector)); + vm.prank(_patchworkOwner); + _prot.withdrawFromProtocol(1); + } + + function testScopeBankers() public { + vm.startPrank(_scopeOwner); + TestFragmentLiteRefNFT lr = new TestFragmentLiteRefNFT(address(_prot)); + _prot.addWhitelist(_scopeName, address(lr)); + _prot.setMintConfiguration(address(lr), IPatchworkProtocol.MintConfig(1000000000, true)); + _prot.addBanker(_scopeName, _user2Address); + vm.stopPrank(); + // mint something just to get some money in the account + IPatchworkProtocol.MintConfig memory mc = _prot.getMintConfiguration(address(lr)); + uint256 mintCost = mc.flatFee; + assertEq(1000000000, mintCost); + _prot.mint{value: mintCost}(_userAddress, address(lr), ""); + assertEq(900000000, _prot.balanceOf(_scopeName)); + assertEq(100000000, _prot.balanceOfProtocol()); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); + _prot.withdraw(_scopeName, 450000000); + vm.prank(_user2Address); + _prot.withdraw(_scopeName, 450000000); + vm.prank(_scopeOwner); + _prot.removeBanker(_scopeName, _user2Address); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); + vm.prank(_user2Address); + _prot.withdraw(_scopeName, 450000000); + // will work and take balance to 0 + vm.prank(_scopeOwner); + _prot.withdraw(_scopeName, 450000000); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InsufficientFunds.selector)); + vm.prank(_scopeOwner); + _prot.withdraw(_scopeName, 1); + } + + function testMints() public { + vm.startPrank(_scopeOwner); + _prot.setScopeRules(_scopeName, false, false, true); + TestFragmentLiteRefNFT lr = new TestFragmentLiteRefNFT(address(_prot)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(lr))); + _prot.setMintConfiguration(address(lr), IPatchworkProtocol.MintConfig(1000000000, true)); + vm.stopPrank(); + // mint something just to get some money in the account + IPatchworkProtocol.MintConfig memory mc = _prot.getMintConfiguration(address(lr)); + uint256 mintCost = mc.flatFee; + assertEq(0, mintCost); // Couldn't be set due to whitelisting + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(lr))); + _prot.mint{value: mintCost}(_userAddress, address(lr), ""); + assertEq(0, _prot.balanceOf(_scopeName)); + assertEq(0, _prot.balanceOfProtocol()); + // Now whitelisted + vm.startPrank(_scopeOwner); + _prot.addWhitelist(_scopeName, address(lr)); + _prot.setMintConfiguration(address(lr), IPatchworkProtocol.MintConfig(1000000000, true)); + vm.stopPrank(); + // mint something just to get some money in the account + mc = _prot.getMintConfiguration(address(lr)); + mintCost = mc.flatFee; + assertEq(1000000000, mintCost); + _prot.mint{value: mintCost}(_userAddress, address(lr), ""); + assertEq(900000000, _prot.balanceOf(_scopeName)); + assertEq(100000000, _prot.balanceOfProtocol()); + } + + function testBatchMints() public { + vm.startPrank(_scopeOwner); + _prot.setScopeRules(_scopeName, false, false, true); + TestFragmentLiteRefNFT lr = new TestFragmentLiteRefNFT(address(_prot)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(lr))); + _prot.setMintConfiguration(address(lr), IPatchworkProtocol.MintConfig(1000000000, true)); + vm.stopPrank(); + // mint something just to get some money in the account + IPatchworkProtocol.MintConfig memory mc = _prot.getMintConfiguration(address(lr)); + uint256 mintCost = mc.flatFee; + assertEq(0, mintCost); // Couldn't be set due to whitelisting + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(lr))); + _prot.mintBatch{value: mintCost}(_userAddress, address(lr), "", 5); + assertEq(0, _prot.balanceOf(_scopeName)); + assertEq(0, _prot.balanceOfProtocol()); + // Now whitelisted + vm.startPrank(_scopeOwner); + _prot.addWhitelist(_scopeName, address(lr)); + _prot.setMintConfiguration(address(lr), IPatchworkProtocol.MintConfig(1000000000, true)); + vm.stopPrank(); + // mint something just to get some money in the account + mc = _prot.getMintConfiguration(address(lr)); + mintCost = mc.flatFee * 5; + assertEq(5000000000, mintCost); + _prot.mintBatch{value: mintCost}(_userAddress, address(lr), "", 5); + assertEq(4500000000, _prot.balanceOf(_scopeName)); + assertEq(500000000, _prot.balanceOfProtocol()); + } + + // TODO patch fees + // TODO assign fees + +} \ No newline at end of file diff --git a/test/PatchworkFragmentMulti.t.sol b/test/PatchworkFragmentMulti.t.sol index a00b994..74ae211 100644 --- a/test/PatchworkFragmentMulti.t.sol +++ b/test/PatchworkFragmentMulti.t.sol @@ -57,10 +57,10 @@ contract PatchworkFragmentMultiTest is Test { function testMultiAssign() public { vm.startPrank(_scopeOwner); - uint256 m1 = _testMultiNFT.mint(_user2Address); - uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 lr2 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 lr3 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 m1 = _testMultiNFT.mint(_user2Address, ""); + uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 lr2 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 lr3 = _testFragmentLiteRefNFT.mint(_userAddress, ""); // must be registered vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testMultiNFT))); _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); @@ -117,9 +117,9 @@ contract PatchworkFragmentMultiTest is Test { vm.startPrank(_scopeOwner); // Enable user assign _prot.setScopeRules(_scopeName, false, true, false); - uint256 m1 = _testMultiNFT.mint(_user2Address); - uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 lr2 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 m1 = _testMultiNFT.mint(_user2Address, ""); + uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 lr2 = _testFragmentLiteRefNFT.mint(_userAddress, ""); // must be registered vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testMultiNFT))); _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); @@ -137,7 +137,7 @@ contract PatchworkFragmentMultiTest is Test { vm.prank(_scopeOwner); _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); vm.prank(_scopeOwner); - uint256 m2 = _testMultiNFT.mint(_userAddress); + uint256 m2 = _testMultiNFT.mint(_userAddress, ""); // This should also work because both are owned by the same user vm.prank(_userAddress); _prot.assignNFT(address(_testMultiNFT), m2, address(_testFragmentLiteRefNFT), lr2); @@ -145,11 +145,11 @@ contract PatchworkFragmentMultiTest is Test { function testGetAssignments() public { vm.startPrank(_scopeOwner); - uint256 m1 = _testMultiNFT.mint(_user2Address); + uint256 m1 = _testMultiNFT.mint(_user2Address, ""); _testFragmentLiteRefNFT.registerReferenceAddress(address(_testMultiNFT)); uint256[] memory liteRefIds = new uint256[](20); for (uint256 i = 0; i < liteRefIds.length; i++) { - liteRefIds[i] = _testFragmentLiteRefNFT.mint(_userAddress); + liteRefIds[i] = _testFragmentLiteRefNFT.mint(_userAddress, ""); _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), liteRefIds[i]); } assertEq(20, _testMultiNFT.getAssignmentCount(m1)); @@ -183,14 +183,14 @@ contract PatchworkFragmentMultiTest is Test { _prot.claimScope(publicScope); _prot.setScopeRules(publicScope, false, false, true); _prot.addWhitelist(publicScope, address(multi)); - uint256 m1 = multi.mint(publicScopeOwner); + uint256 m1 = multi.mint(publicScopeOwner, ""); vm.stopPrank(); // mow we have a multi fragment in "publicmulti" scope and another scope wants to use it - both require whitelisting vm.startPrank(_scopeOwner); _prot.setScopeRules(_scopeName, false, false, true); _prot.addWhitelist(_scopeName, address(_testFragmentLiteRefNFT)); _testFragmentLiteRefNFT.registerReferenceAddress(address(multi)); - uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); _prot.assignNFT(address(multi), m1, address(_testFragmentLiteRefNFT), lr1); } } \ No newline at end of file diff --git a/test/PatchworkNFT.t.sol b/test/PatchworkNFT.t.sol index 98d85a0..6ce61a9 100644 --- a/test/PatchworkNFT.t.sol +++ b/test/PatchworkNFT.t.sol @@ -49,125 +49,125 @@ contract PatchworkNFTTest is Test { } function testLoadStorePackedMetadataSlot() public { - _testPatchworkNFT.mint(_userAddress, 1); + uint256 n = _testPatchworkNFT.mint(_userAddress, ""); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); - _testPatchworkNFT.storePackedMetadataSlot(1, 0, 0x505050); + _testPatchworkNFT.storePackedMetadataSlot(n, 0, 0x505050); vm.prank(_scopeOwner); - _testPatchworkNFT.storePackedMetadataSlot(1, 0, 0x505050); - assertEq(0x505050, _testPatchworkNFT.loadPackedMetadataSlot(1, 0)); + _testPatchworkNFT.storePackedMetadataSlot(n, 0, 0x505050); + assertEq(0x505050, _testPatchworkNFT.loadPackedMetadataSlot(n, 0)); } function testTransferFrom() public { // TODO make sure these are calling checkTransfer on proto - _testPatchworkNFT.mint(_userAddress, 1); - assertEq(_userAddress, _testPatchworkNFT.ownerOf(1)); + uint256 n = _testPatchworkNFT.mint(_userAddress, ""); + assertEq(_userAddress, _testPatchworkNFT.ownerOf(n)); vm.prank(_userAddress); - _testPatchworkNFT.transferFrom(_userAddress, _user2Address, 1); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.transferFrom(_userAddress, _user2Address, n); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); vm.prank(_user2Address); - _testPatchworkNFT.safeTransferFrom(_user2Address, _userAddress, 1); - assertEq(_userAddress, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.safeTransferFrom(_user2Address, _userAddress, n); + assertEq(_userAddress, _testPatchworkNFT.ownerOf(n)); vm.prank(_userAddress); - _testPatchworkNFT.safeTransferFrom(_userAddress, _user2Address, 1, bytes("abcd")); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.safeTransferFrom(_userAddress, _user2Address, n, bytes("abcd")); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); // test wrong user revert vm.startPrank(_userAddress); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); vm.expectRevert("ERC721: caller is not token owner or approved"); - _testPatchworkNFT.transferFrom(_user2Address, _userAddress, 1); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.transferFrom(_user2Address, _userAddress, n); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); vm.expectRevert("ERC721: caller is not token owner or approved"); - _testPatchworkNFT.safeTransferFrom(_user2Address, _userAddress, 1); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.safeTransferFrom(_user2Address, _userAddress, n); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); vm.expectRevert("ERC721: caller is not token owner or approved"); - _testPatchworkNFT.safeTransferFrom(_user2Address, _userAddress, 1, bytes("abcd")); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.safeTransferFrom(_user2Address, _userAddress, n, bytes("abcd")); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); } function testLockFreezeSeparation() public { - _testPatchworkNFT.mint(_userAddress, 1); + uint256 n = _testPatchworkNFT.mint(_userAddress, ""); vm.startPrank(_userAddress); - assertFalse(_testPatchworkNFT.locked(1)); - _testPatchworkNFT.setLocked(1, true); - assertTrue(_testPatchworkNFT.locked(1)); - assertFalse(_testPatchworkNFT.frozen(1)); - _testPatchworkNFT.setFrozen(1, true); - assertTrue(_testPatchworkNFT.frozen(1)); - assertTrue(_testPatchworkNFT.locked(1)); - _testPatchworkNFT.setLocked(1, false); - assertTrue(_testPatchworkNFT.frozen(1)); - assertFalse(_testPatchworkNFT.locked(1)); - _testPatchworkNFT.setFrozen(1, false); - assertFalse(_testPatchworkNFT.frozen(1)); - assertFalse(_testPatchworkNFT.locked(1)); - _testPatchworkNFT.setFrozen(1, true); - assertTrue(_testPatchworkNFT.frozen(1)); - assertFalse(_testPatchworkNFT.locked(1)); - _testPatchworkNFT.setLocked(1, true); - assertTrue(_testPatchworkNFT.frozen(1)); - assertTrue(_testPatchworkNFT.locked(1)); + assertFalse(_testPatchworkNFT.locked(n)); + _testPatchworkNFT.setLocked(n, true); + assertTrue(_testPatchworkNFT.locked(n)); + assertFalse(_testPatchworkNFT.frozen(n)); + _testPatchworkNFT.setFrozen(n, true); + assertTrue(_testPatchworkNFT.frozen(n)); + assertTrue(_testPatchworkNFT.locked(n)); + _testPatchworkNFT.setLocked(n, false); + assertTrue(_testPatchworkNFT.frozen(n)); + assertFalse(_testPatchworkNFT.locked(n)); + _testPatchworkNFT.setFrozen(n, false); + assertFalse(_testPatchworkNFT.frozen(n)); + assertFalse(_testPatchworkNFT.locked(n)); + _testPatchworkNFT.setFrozen(n, true); + assertTrue(_testPatchworkNFT.frozen(n)); + assertFalse(_testPatchworkNFT.locked(n)); + _testPatchworkNFT.setLocked(n, true); + assertTrue(_testPatchworkNFT.frozen(n)); + assertTrue(_testPatchworkNFT.locked(n)); } function testTransferFromWithFreezeNonce() public { // TODO make sure these are calling checkTransfer on proto - _testPatchworkNFT.mint(_userAddress, 1); + uint256 n = _testPatchworkNFT.mint(_userAddress, ""); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); - _testPatchworkNFT.setFrozen(1, true); + _testPatchworkNFT.setFrozen(n, true); vm.prank(_userAddress); - _testPatchworkNFT.setFrozen(1, true); - assertEq(_userAddress, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.setFrozen(n, true); + assertEq(_userAddress, _testPatchworkNFT.ownerOf(n)); vm.prank(_userAddress); - _testPatchworkNFT.transferFromWithFreezeNonce(_userAddress, _user2Address, 1, 0); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.transferFromWithFreezeNonce(_userAddress, _user2Address, n, 0); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); vm.prank(_user2Address); - _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, 0); - assertEq(_userAddress, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, n, 0); + assertEq(_userAddress, _testPatchworkNFT.ownerOf(n)); vm.prank(_userAddress); - _testPatchworkNFT.safeTransferFromWithFreezeNonce(_userAddress, _user2Address, 1, bytes("abcd"), 0); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_userAddress, _user2Address, n, bytes("abcd"), 0); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); vm.startPrank(_user2Address); // test not frozen revert - _testPatchworkNFT.setFrozen(1, false); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); - - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, _testPatchworkNFT, 1)); - _testPatchworkNFT.transferFromWithFreezeNonce(_user2Address, _userAddress, 1, 1); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, _testPatchworkNFT, 1)); - _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, 1); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, _testPatchworkNFT, 1)); - _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, bytes("abcd"), 1); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.setFrozen(n, false); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); + + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, _testPatchworkNFT, n)); + _testPatchworkNFT.transferFromWithFreezeNonce(_user2Address, _userAddress, n, 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, _testPatchworkNFT, n)); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, n, 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotFrozen.selector, _testPatchworkNFT, n)); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, n, bytes("abcd"), 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); // test incorrect nonce revert - _testPatchworkNFT.setFrozen(1, true); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, _testPatchworkNFT, 1, 0)); - _testPatchworkNFT.transferFromWithFreezeNonce(_user2Address, _userAddress, 1, 0); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, _testPatchworkNFT, 1, 0)); - _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, 0); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, _testPatchworkNFT, 1, 0)); - _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, bytes("abcd"), 0); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.setFrozen(n, true); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, _testPatchworkNFT, n, 0)); + _testPatchworkNFT.transferFromWithFreezeNonce(_user2Address, _userAddress, n, 0); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, _testPatchworkNFT, n, 0)); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, n, 0); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectNonce.selector, _testPatchworkNFT, n, 0)); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, n, bytes("abcd"), 0); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); vm.stopPrank(); // test wrong user revert vm.startPrank(_userAddress); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); vm.expectRevert("ERC721: caller is not token owner or approved"); - _testPatchworkNFT.transferFromWithFreezeNonce(_user2Address, _userAddress, 1, 1); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.transferFromWithFreezeNonce(_user2Address, _userAddress, n, 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); vm.expectRevert("ERC721: caller is not token owner or approved"); - _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, 1); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, n, 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); vm.expectRevert("ERC721: caller is not token owner or approved"); - _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, 1, bytes("abcd"), 1); - assertEq(_user2Address, _testPatchworkNFT.ownerOf(1)); + _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, n, bytes("abcd"), 1); + assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); vm.stopPrank(); } } \ No newline at end of file diff --git a/test/PatchworkNFTReferences.t.sol b/test/PatchworkNFTReferences.t.sol index 87293e7..1ecf226 100644 --- a/test/PatchworkNFTReferences.t.sol +++ b/test/PatchworkNFTReferences.t.sol @@ -64,7 +64,7 @@ contract PatchworkNFTCombinedTest is Test { // test assign perms uint256 baseTokenId = _testBaseNFT.mint(_userAddress); - uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress, ""); assertEq(_userAddress, _testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); // TODO why doesn't this cover the branch != address(0) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); vm.prank(_user2Address); @@ -91,7 +91,7 @@ contract PatchworkNFTCombinedTest is Test { vm.prank(_userAddress); // can't call directly _testFragmentLiteRefNFT.unassign(fragmentTokenId); - uint256 newFrag = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 newFrag = _testFragmentLiteRefNFT.mint(_userAddress, ""); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); _testPatchLiteRefNFT.redactReferenceAddress(refIdx); vm.prank(_scopeOwner); diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index 8f5f520..b99cb5a 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -167,7 +167,7 @@ contract PatchworkProtocolTest is Test { uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); _prot.addWhitelist(_scopeName, address(_testPatchLiteRefNFT)); _prot.addWhitelist(_scopeName, address(_testFragmentLiteRefNFT)); - uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress, ""); assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _userAddress); //Register artifactNFT to _testPatchLiteRefNFT _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); @@ -208,7 +208,7 @@ contract PatchworkProtocolTest is Test { vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.AlreadyPatched.selector, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT))); patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); - uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress, ""); assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _userAddress); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testFragmentLiteRefNFT))); @@ -247,9 +247,9 @@ contract PatchworkProtocolTest is Test { function testScopeDoesNotExist() public { vm.startPrank(_scopeOwner); - uint256 fragmentTokenId1 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 fragmentTokenId2 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 multi1 = _testMultiFragmentNFT.mint(_userAddress); + uint256 fragmentTokenId1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 fragmentTokenId2 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 multi1 = _testMultiFragmentNFT.mint(_userAddress, ""); //Register _testPatchLiteRefNFT to _testPatchLiteRefNFT _testFragmentLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); _testFragmentLiteRefNFT.registerReferenceAddress(address(_testMultiFragmentNFT)); @@ -307,8 +307,8 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_userAddress); uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); - uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 user2FragmentTokenId = _testFragmentLiteRefNFT.mint(_user2Address); + uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 user2FragmentTokenId = _testFragmentLiteRefNFT.mint(_user2Address, ""); assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _userAddress); // Not whitelisted @@ -357,7 +357,7 @@ contract PatchworkProtocolTest is Test { _prot.setScopeRules(_scopeName, false, false, false); uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); - uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_user2Address); + uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_user2Address, ""); assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _user2Address); //Register artifactNFT to _testPatchLiteRefNFT _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); @@ -370,8 +370,8 @@ contract PatchworkProtocolTest is Test { _prot.unassignSingleNFT(address(1), 1); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); - uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress, ""); vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); @@ -444,7 +444,7 @@ contract PatchworkProtocolTest is Test { vm.expectRevert(); // not unassignable _prot.unassignMultiNFTDirect(address(1), 1, address(1), 1, 0); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); - uint256 fragment1 = _testMultiFragmentNFT.mint(_userAddress); + uint256 fragment1 = _testMultiFragmentNFT.mint(_userAddress, ""); vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); @@ -470,7 +470,7 @@ contract PatchworkProtocolTest is Test { for (uint8 i = 0; i < 8; i++) { fragmentAddresses[i] = address(_testFragmentLiteRefNFT); - fragments[i] = _testFragmentLiteRefNFT.mint(_userAddress); + fragments[i] = _testFragmentLiteRefNFT.mint(_userAddress, ""); } // Fragment must be registered @@ -551,7 +551,7 @@ contract PatchworkProtocolTest is Test { address[] memory otherUserAddr = new address[](1); uint256[] memory otherUserFrag = new uint256[](1); otherUserAddr[0] = address(_testFragmentLiteRefNFT); - otherUserFrag[0] = _testFragmentLiteRefNFT.mint(_user2Address); + otherUserFrag[0] = _testFragmentLiteRefNFT.mint(_user2Address, ""); // Target and fragment not same owner vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); vm.prank(_scopeOwner); @@ -591,8 +591,8 @@ contract PatchworkProtocolTest is Test { for (uint8 i = 0; i < 8; i++) { fragmentAddresses[i] = address(_testFragmentLiteRefNFT); - fragments[i] = _testFragmentLiteRefNFT.mint(_userAddress); - user2Fragments[i] = _testFragmentLiteRefNFT.mint(_user2Address); + fragments[i] = _testFragmentLiteRefNFT.mint(_userAddress, ""); + user2Fragments[i] = _testFragmentLiteRefNFT.mint(_user2Address, ""); } vm.stopPrank(); @@ -612,7 +612,7 @@ contract PatchworkProtocolTest is Test { address[] memory otherUserAddr = new address[](1); uint256[] memory otherUserFrag = new uint256[](1); otherUserAddr[0] = address(_testFragmentLiteRefNFT); - otherUserFrag[0] = _testFragmentLiteRefNFT.mint(_user2Address); + otherUserFrag[0] = _testFragmentLiteRefNFT.mint(_user2Address, ""); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); _prot.batchAssignNFT(otherUserAddr, otherUserFrag, address(_testPatchLiteRefNFT), patchTokenId); @@ -631,9 +631,9 @@ contract PatchworkProtocolTest is Test { } function testTransferLogs() public { - uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 fragment3 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 fragment3 = _testFragmentLiteRefNFT.mint(_userAddress, ""); vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); @@ -661,9 +661,9 @@ contract PatchworkProtocolTest is Test { } function testUpdateOwnership() public { - uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 fragment3 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 fragment3 = _testFragmentLiteRefNFT.mint(_userAddress, ""); vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); @@ -702,9 +702,9 @@ contract PatchworkProtocolTest is Test { function testLocks() public { uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); - uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress); - _testPatchworkNFT.mint(_userAddress, 1); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 n = _testPatchworkNFT.mint(_userAddress, ""); vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); @@ -736,7 +736,7 @@ contract PatchworkProtocolTest is Test { // only owner may lock vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); vm.prank(_user2Address); - _testPatchworkNFT.setLocked(1, true); + _testPatchworkNFT.setLocked(n, true); // an assigned fragment is locked implicitly assertEq(false, _testFragmentLiteRefNFT.locked(fragment1)); @@ -767,8 +767,8 @@ contract PatchworkProtocolTest is Test { function testFreezes() public { uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); - uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress, ""); vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); @@ -853,8 +853,8 @@ contract PatchworkProtocolTest is Test { function testSpoofedTransfer1() public { vm.startPrank(_scopeOwner); // create a patchworkliteref but manually put in an entry that isn't assigned to it (spoof ownership) - uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 fragment2 = _testFragmentLiteRefNFT.mint(_userAddress, ""); _testFragmentLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); (uint64 ref, ) = _testFragmentLiteRefNFT.getLiteReference(address(_testFragmentLiteRefNFT), fragment2); _testFragmentLiteRefNFT.addReference(fragment1, ref); @@ -868,7 +868,7 @@ contract PatchworkProtocolTest is Test { function testSpoofedTransfer2() public { vm.startPrank(_scopeOwner); // create a patchworkliteref but manually put in an entry that isn't assigned to it (spoof ownership) - uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress); + uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); _testFragmentLiteRefNFT.registerReferenceAddress(address(_testBaseNFT)); (uint64 ref, ) = _testFragmentLiteRefNFT.getLiteReference(address(_testBaseNFT), _testBaseNFTTokenId); @@ -887,8 +887,8 @@ contract PatchworkProtocolTest is Test { _prot.setScopeRules(_scopeName, false, false, false); _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); _testFragmentLiteRefNFT.registerReferenceAddress(address(testFrag2)); - uint256 frag1 = _testFragmentLiteRefNFT.mint(_userAddress); - uint256 frag2 = testFrag2.mint(_userAddress); + uint256 frag1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 frag2 = testFrag2.mint(_userAddress, ""); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); _prot.assignNFT(address(_testFragmentLiteRefNFT), frag1, address(_testPatchLiteRefNFT), patchTokenId); diff --git a/test/TestDynamicArrayLiteRefNFT.t.sol b/test/TestDynamicArrayLiteRefNFT.t.sol index 60daeac..300101c 100644 --- a/test/TestDynamicArrayLiteRefNFT.t.sol +++ b/test/TestDynamicArrayLiteRefNFT.t.sol @@ -39,7 +39,7 @@ contract PatchworkAccountPatchTest is Test { function testDynamics() public { TestDynamicArrayLiteRefNFT nft = new TestDynamicArrayLiteRefNFT(address(_prot)); nft.registerReferenceAddress(address(0x55)); - uint256 m = nft.mint(_userAddress); + uint256 m = nft.mint(_userAddress, ""); assertEq(0, nft.getDynamicReferenceCount(m)); for (uint256 i = 0; i < 9; i++) { (uint64 ref,) = nft.getLiteReference(address(0x55), i); @@ -73,7 +73,7 @@ contract PatchworkAccountPatchTest is Test { function testBatchAdd() public { TestDynamicArrayLiteRefNFT nft = new TestDynamicArrayLiteRefNFT(address(_prot)); nft.registerReferenceAddress(address(0x55)); - uint256 m = nft.mint(_userAddress); + uint256 m = nft.mint(_userAddress, ""); assertEq(0, nft.getDynamicReferenceCount(m)); uint64[] memory refs = new uint64[](11); for (uint256 i = 0; i < 11; i++) { diff --git a/test/nfts/TestDynamicArrayLiteRefNFT.sol b/test/nfts/TestDynamicArrayLiteRefNFT.sol index 4547e58..ca1cda5 100644 --- a/test/nfts/TestDynamicArrayLiteRefNFT.sol +++ b/test/nfts/TestDynamicArrayLiteRefNFT.sol @@ -11,6 +11,7 @@ pragma solidity ^0.8.13; import "../../src/PatchworkNFT.sol"; import "../../src/PatchworkLiteRef.sol"; +import "../../src/IPatchworkMintable.sol"; import "forge-std/console.sol"; struct TestDynamicArrayLiteRefNFTMetadata { @@ -28,7 +29,7 @@ struct DynamicLiteRefs { mapping(uint64 => uint256) idx; } -contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef { +contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef, IPatchworkMintable { uint256 _nextTokenId; @@ -54,13 +55,24 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef { _manager = manager_; } - function mint(address to) external returns (uint256 tokenId) { + function getScopeName() public view override (PatchworkNFT, IPatchworkScoped) returns (string memory scopeName) { + return PatchworkNFT.getScopeName(); + } + + function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { tokenId = _nextTokenId; _nextTokenId++; _safeMint(to, tokenId); _metadataStorage[tokenId] = new uint256[](1); _dynamicLiterefStorage[tokenId].slots = new uint256[](0); - } + } + + function mintBatch(address to, bytes calldata data, uint256 quantity) public payable returns (uint256[] memory tokenIds) { + tokenIds = new uint256[](quantity); + for (uint256 i = 0; i < quantity; i++) { + tokenIds[i] = mint(to, data); + } + } /* Hard coded prototype schema is: diff --git a/test/nfts/TestFragmentLiteRefNFT.sol b/test/nfts/TestFragmentLiteRefNFT.sol index 63d4c49..f12feae 100644 --- a/test/nfts/TestFragmentLiteRefNFT.sol +++ b/test/nfts/TestFragmentLiteRefNFT.sol @@ -11,6 +11,7 @@ pragma solidity ^0.8.13; import "../../src/PatchworkFragmentSingle.sol"; import "../../src/PatchworkLiteRef.sol"; +import "../../src/IPatchworkMintable.sol"; enum FragmentType { BASE, @@ -26,7 +27,7 @@ struct TestFragmentLiteRefNFTMetadata { string name; } -contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef { +contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef, IPatchworkMintable { uint256 _nextTokenId; bool _testLockOverride; @@ -41,15 +42,27 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef { // ERC-165 function supportsInterface(bytes4 interfaceID) public view virtual override(PatchworkFragmentSingle, PatchworkLiteRef) returns (bool) { return PatchworkLiteRef.supportsInterface(interfaceID) || - PatchworkFragmentSingle.supportsInterface(interfaceID); + PatchworkFragmentSingle.supportsInterface(interfaceID) || + interfaceID == type(IPatchworkMintable).interfaceId; } - function mint(address to) external returns (uint256 tokenId) { + function getScopeName() public view override (PatchworkFragmentSingle, IPatchworkScoped) returns (string memory scopeName) { + return PatchworkNFT.getScopeName(); + } + + function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { tokenId = _nextTokenId; _nextTokenId++; _safeMint(to, tokenId); _metadataStorage[tokenId] = new uint256[](3); } + + function mintBatch(address to, bytes calldata data, uint256 quantity) public payable returns (uint256[] memory tokenIds) { + tokenIds = new uint256[](quantity); + for (uint256 i = 0; i < quantity; i++) { + tokenIds[i] = mint(to, data); + } + } function schemaURI() pure external returns (string memory) { return "https://mything/my-fragment-metadata.json"; diff --git a/test/nfts/TestMultiFragmentNFT.sol b/test/nfts/TestMultiFragmentNFT.sol index cb95bb5..1cefd2b 100644 --- a/test/nfts/TestMultiFragmentNFT.sol +++ b/test/nfts/TestMultiFragmentNFT.sol @@ -3,23 +3,40 @@ pragma solidity ^0.8.13; import "../../src/PatchworkFragmentMulti.sol"; import "../../src/PatchworkLiteRef.sol"; +import "../../src/IPatchworkMintable.sol"; struct TestMultiFragmentNFTMetadata { uint8 nothing; } -contract TestMultiFragmentNFT is PatchworkFragmentMulti { +contract TestMultiFragmentNFT is PatchworkFragmentMulti, IPatchworkMintable { uint256 _nextTokenId; constructor (address _manager) PatchworkNFT("testscope", "TestMultiFragmentNFT", "TFLR", msg.sender, _manager) { } - function mint(address to) external returns (uint256 tokenId) { + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return PatchworkFragmentMulti.supportsInterface(interfaceID) || + interfaceID == type(IPatchworkMintable).interfaceId; + } + + function getScopeName() public view override (PatchworkFragmentMulti, IPatchworkScoped) returns (string memory scopeName) { + return PatchworkNFT.getScopeName(); + } + + function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { tokenId = _nextTokenId; _nextTokenId++; _safeMint(to, tokenId); _metadataStorage[tokenId] = new uint256[](1); } + + function mintBatch(address to, bytes calldata data, uint256 quantity) public payable returns (uint256[] memory tokenIds) { + tokenIds = new uint256[](quantity); + for (uint256 i = 0; i < quantity; i++) { + tokenIds[i] = mint(to, data); + } + } function setScopeName(string memory scopeName) public { // For testing only diff --git a/test/nfts/TestPatchworkNFT.sol b/test/nfts/TestPatchworkNFT.sol index 3914fb5..0ceb02c 100644 --- a/test/nfts/TestPatchworkNFT.sol +++ b/test/nfts/TestPatchworkNFT.sol @@ -2,8 +2,11 @@ pragma solidity ^0.8.13; import "../../src/PatchworkNFT.sol"; +import "../../src/IPatchworkMintable.sol"; -contract TestPatchworkNFT is PatchworkNFT { +contract TestPatchworkNFT is PatchworkNFT, IPatchworkMintable { + + uint256 _nextTokenId; struct TestPatchworkNFTMetadata { uint256 thing; @@ -12,6 +15,11 @@ contract TestPatchworkNFT is PatchworkNFT { constructor(address manager_) PatchworkNFT("testscope", "TestPatchworkNFT", "TPLR", msg.sender, manager_) { } + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return PatchworkNFT.supportsInterface(interfaceID) || + interfaceID == type(IPatchworkMintable).interfaceId; + } + function schemaURI() pure external returns (string memory) { return "https://mything/my-nft-metadata.json"; } @@ -26,8 +34,21 @@ contract TestPatchworkNFT is PatchworkNFT { return MetadataSchema(1, entries); } - function mint(address to, uint256 tokenId) public { + function getScopeName() public view override (PatchworkNFT, IPatchworkScoped) returns (string memory scopeName) { + return PatchworkNFT.getScopeName(); + } + + function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { + tokenId = _nextTokenId; + _nextTokenId++; _mint(to, tokenId); _metadataStorage[tokenId] = new uint256[](1); } + + function mintBatch(address to, bytes calldata data, uint256 quantity) public payable returns (uint256[] memory tokenIds) { + tokenIds = new uint256[](quantity); + for (uint256 i = 0; i < quantity; i++) { + tokenIds[i] = mint(to, data); + } + } } \ No newline at end of file From 99ddda4b2497c64bf57dd9e83c1395aafe1d9b7e Mon Sep 17 00:00:00 2001 From: Rob Green Date: Tue, 28 Nov 2023 20:38:08 -0800 Subject: [PATCH 24/63] Mass renaming and standardizing (#46) * first big renaming * Renamings * More renaming * smaller renaming * More renaming * Added all slots metadata store/load to base 721 * More renaming --- src/IPatchwork1155Patch.sol | 12 +- src/{IPatchworkNFT.sol => IPatchwork721.sol} | 33 ++- ...gnableNFT.sol => IPatchworkAssignable.sol} | 2 +- src/IPatchworkLiteRef.sol | 32 +-- ...eNFT.sol => IPatchworkMultiAssignable.sol} | 4 +- src/IPatchworkPatch.sol | 12 +- src/IPatchworkProtocol.sol | 169 ++++++------ ...NFT.sol => IPatchworkSingleAssignable.sol} | 6 +- src/Patchwork1155Patch.sol | 26 +- src/{PatchworkNFT.sol => Patchwork721.sol} | 64 +++-- src/PatchworkAccountPatch.sol | 10 +- src/PatchworkFragmentMulti.sol | 20 +- src/PatchworkFragmentSingle.sol | 23 +- src/PatchworkPatch.sol | 32 +-- src/PatchworkProtocol.sol | 251 +++++++++--------- test/Patchwork1155Patch.t.sol | 20 +- test/PatchworkAccountPatch.t.sol | 16 +- test/PatchworkFragmentMulti.t.sol | 62 ++--- test/PatchworkFragmentSingle.t.sol | 6 +- test/PatchworkNFT.t.sol | 2 +- test/PatchworkNFTReferences.t.sol | 14 +- test/PatchworkPatch.t.sol | 24 +- test/PatchworkProtocol.t.sol | 194 +++++++------- test/TestDynamicArrayLiteRefNFT.t.sol | 2 +- test/nfts/Test1155PatchNFT.sol | 2 +- test/nfts/TestAccountPatchNFT.sol | 2 +- test/nfts/TestDynamicArrayLiteRefNFT.sol | 28 +- test/nfts/TestFragmentLiteRefNFT.sol | 19 +- test/nfts/TestMultiFragmentNFT.sol | 4 +- test/nfts/TestPatchFragmentNFT.sol | 2 +- test/nfts/TestPatchLiteRefNFT.sol | 16 +- test/nfts/TestPatchworkNFT.sol | 12 +- 32 files changed, 567 insertions(+), 554 deletions(-) rename src/{IPatchworkNFT.sol => IPatchwork721.sol} (85%) rename src/{IPatchworkAssignableNFT.sol => IPatchworkAssignable.sol} (94%) rename src/{IPatchworkMultiAssignableNFT.sol => IPatchworkMultiAssignable.sol} (92%) rename src/{IPatchworkSingleAssignableNFT.sol => IPatchworkSingleAssignable.sol} (89%) rename src/{PatchworkNFT.sol => Patchwork721.sol} (82%) diff --git a/src/IPatchwork1155Patch.sol b/src/IPatchwork1155Patch.sol index c3c4597..001e0ec 100644 --- a/src/IPatchwork1155Patch.sol +++ b/src/IPatchwork1155Patch.sol @@ -12,20 +12,20 @@ interface IPatchwork1155Patch is IPatchworkScoped { /** @notice Creates a new token for the owner, representing a patch @param to Address of the owner of the patch token - @param originalNFTAddress Address of the original NFT - @param originalNFTTokenId ID of the original NFT token + @param originalAddress Address of the original 1155 + @param originalTokenId ID of the original 1155 token @param originalAccount Address of the original 1155 account @return tokenId ID of the newly minted token */ - function mintPatch(address to, address originalNFTAddress, uint256 originalNFTTokenId, address originalAccount) external returns (uint256 tokenId); + function mintPatch(address to, address originalAddress, uint256 originalTokenId, address originalAccount) external returns (uint256 tokenId); /** @notice Returns the token ID (if it exists) for an NFT that may have been patched @dev Requires reverse storage enabled - @param originalNFTAddress Address of the original NFT - @param originalNFTTokenId ID of the original NFT token + @param originalAddress Address of the original 1155 + @param originalTokenId ID of the original 1155 token @param originalAccount Address of the original 1155 account @return tokenId ID of the newly minted token */ - function getTokenIdForOriginalNFT(address originalNFTAddress, uint256 originalNFTTokenId, address originalAccount) external returns (uint256 tokenId); + function getTokenIdForOriginal1155(address originalAddress, uint256 originalTokenId, address originalAccount) external returns (uint256 tokenId); } \ No newline at end of file diff --git a/src/IPatchworkNFT.sol b/src/IPatchwork721.sol similarity index 85% rename from src/IPatchworkNFT.sol rename to src/IPatchwork721.sol index 8937d9f..c9aa0b2 100644 --- a/src/IPatchworkNFT.sol +++ b/src/IPatchwork721.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "./IERC5192.sol"; import "./IPatchworkScoped.sol"; /** -@title Patchwork Protocol NFT Interface Metadata +@title Patchwork Protocol Interface Metadata @author Runic Labs, Inc -@notice Metadata for IPatchworkNFT and related contract interfaces +@notice Metadata for IPatchwork721 and related contract interfaces */ -interface PatchworkNFTInterfaceMeta { +interface IPatchworkMetadata { /** @notice Enumeration of possible field data types. @dev This defines the various basic data types for the fields. @@ -70,13 +70,12 @@ interface PatchworkNFTInterfaceMeta { } } -// TODO - Protocol assumes this is IERC721. Should we just declare it here? /** -@title Patchwork Protocol NFT Interface +@title Patchwork Protocol 721 Interface @author Runic Labs, Inc @notice Interface for contracts supporting Patchwork metadata standard */ -interface IPatchworkNFT is IPatchworkScoped, PatchworkNFTInterfaceMeta, IERC5192 { +interface IPatchwork721 is IPatchworkScoped, IPatchworkMetadata, IERC5192, IERC721 { /** @notice Emitted when the freeze status is changed to frozen. @param tokenId The identifier for a token. @@ -90,15 +89,15 @@ interface IPatchworkNFT is IPatchworkScoped, PatchworkNFTInterfaceMeta, IERC5192 event Thawed(uint256 indexed tokenId); /** - @notice Emitted when the permissions are changed for an NFT + @notice Emitted when the permissions are changed @param to The address the permissions are assigned to @param permissions The permissions */ event PermissionChange(address indexed to, uint256 permissions); /** - @notice Emitted when the schema has changed for an NFT - @param addr the address of the NFT + @notice Emitted when the schema has changed + @param addr the address of the Patchwork721 */ event SchemaChange(address indexed addr); @@ -136,6 +135,13 @@ interface IPatchworkNFT is IPatchworkScoped, PatchworkNFTInterfaceMeta, IERC5192 */ function storePackedMetadataSlot(uint256 tokenId, uint256 slot, uint256 data) external; + /** + @notice Stores packed metadata for a given token ID + @param tokenId ID of the token + @param data Metadata to store + */ + function storePackedMetadata(uint256 tokenId, uint256[] memory data) external; + /** @notice Loads packed metadata for a given token ID and slot @param tokenId ID of the token @@ -144,6 +150,13 @@ interface IPatchworkNFT is IPatchworkScoped, PatchworkNFTInterfaceMeta, IERC5192 */ function loadPackedMetadataSlot(uint256 tokenId, uint256 slot) external view returns (uint256); + /** + @notice Loads packed metadata for a given token ID + @param tokenId ID of the token + @return uint256[] the raw slot data as a uint256 array + */ + function loadPackedMetadata(uint256 tokenId) external view returns (uint256[] memory); + /** @notice Returns the freeze nonce for a given token ID @param tokenId ID of the token diff --git a/src/IPatchworkAssignableNFT.sol b/src/IPatchworkAssignable.sol similarity index 94% rename from src/IPatchworkAssignableNFT.sol rename to src/IPatchworkAssignable.sol index cedd0fa..dccddb7 100644 --- a/src/IPatchworkAssignableNFT.sol +++ b/src/IPatchworkAssignable.sol @@ -8,7 +8,7 @@ import "./IPatchworkScoped.sol"; @author Runic Labs, Inc @notice Interface for contracts supporting Patchwork assignment */ -interface IPatchworkAssignableNFT is IPatchworkScoped { +interface IPatchworkAssignable is IPatchworkScoped { /** @notice Assigns a token to another @param ourTokenId ID of our token diff --git a/src/IPatchworkLiteRef.sol b/src/IPatchworkLiteRef.sol index 11cc6e3..dbab547 100644 --- a/src/IPatchworkLiteRef.sol +++ b/src/IPatchworkLiteRef.sol @@ -89,42 +89,42 @@ interface IPatchworkLiteRef { function addReference(uint256 tokenId, uint64 liteRef) external; /** - @notice Adds multiple references to a token + @notice Adds a reference to a token @param tokenId ID of the token - @param liteRefs Array of lite references to add + @param liteRef LiteRef to add + @param targetMetadataId The metadata ID on the target to assign to */ - function batchAddReferences(uint256 tokenId, uint64[] calldata liteRefs) external; + function addReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) external; /** - @notice Removes a reference from a token + @notice Adds multiple references to a token @param tokenId ID of the token - @param liteRef Lite reference to remove + @param liteRefs Array of lite references to add */ - function removeReference(uint256 tokenId, uint64 liteRef) external; + function addReferenceBatch(uint256 tokenId, uint64[] calldata liteRefs) external; /** - @notice Adds a reference to a token + @notice Adds multiple references to a token @param tokenId ID of the token - @param liteRef LiteRef to add - @param targetMetadataId The metadata ID on the target NFT to unassign from + @param liteRefs Array of lite references to add + @param targetMetadataId The metadata ID on the target to assign to */ - function addReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) external; + function addReferenceBatch(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) external; /** - @notice Adds multiple references to a token + @notice Removes a reference from a token @param tokenId ID of the token - @param liteRefs Array of lite references to add - @param targetMetadataId The metadata ID on the target NFT to unassign from + @param liteRef Lite reference to remove */ - function batchAddReferencesDirect(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) external; + function removeReference(uint256 tokenId, uint64 liteRef) external; /** @notice Removes a reference from a token @param tokenId ID of the token @param liteRef Lite reference to remove - @param targetMetadataId The metadata ID on the target NFT to unassign from + @param targetMetadataId The metadata ID on the target to unassign from */ - function removeReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) external; + function removeReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) external; /** @notice Loads a reference address and token ID at a given index diff --git a/src/IPatchworkMultiAssignableNFT.sol b/src/IPatchworkMultiAssignable.sol similarity index 92% rename from src/IPatchworkMultiAssignableNFT.sol rename to src/IPatchworkMultiAssignable.sol index fe4ee3b..adee6d8 100644 --- a/src/IPatchworkMultiAssignableNFT.sol +++ b/src/IPatchworkMultiAssignable.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "./IPatchworkAssignableNFT.sol"; +import "./IPatchworkAssignable.sol"; /** @title Patchwork Protocol Assignable NFT Interface @author Runic Labs, Inc @notice Interface for contracts supporting Patchwork assignment */ -interface IPatchworkMultiAssignableNFT is IPatchworkAssignableNFT { +interface IPatchworkMultiAssignable is IPatchworkAssignable { struct Assignment { address tokenAddr; /// The address of the external NFT contract. diff --git a/src/IPatchworkPatch.sol b/src/IPatchworkPatch.sol index 396a383..5632820 100644 --- a/src/IPatchworkPatch.sol +++ b/src/IPatchworkPatch.sol @@ -12,11 +12,11 @@ interface IPatchworkPatch is IPatchworkScoped { /** @notice Creates a new token for the owner, representing a patch @param owner Address of the owner of the token - @param originalNFTAddress Address of the original NFT - @param originalNFTTokenId ID of the original NFT token + @param originalAddress Address of the original 721 + @param originalTokenId The original 721's tokenId @return tokenId ID of the newly minted token */ - function mintPatch(address owner, address originalNFTAddress, uint256 originalNFTTokenId) external returns (uint256 tokenId); + function mintPatch(address owner, address originalAddress, uint256 originalTokenId) external returns (uint256 tokenId); /** @notice Updates the real underlying ownership of a token in storage (if different from current) @@ -34,9 +34,9 @@ interface IPatchworkPatch is IPatchworkScoped { /** @notice Returns the token ID (if it exists) for an NFT that may have been patched @dev Requires reverse storage enabled - @param originalNFTAddress Address of the original NFT - @param originalNFTTokenId ID of the original NFT token + @param originalAddress Address of the original 721 + @param originalTokenId The original 721's tokenId @return tokenId ID of the newly minted token */ - function getTokenIdForOriginalNFT(address originalNFTAddress, uint256 originalNFTTokenId) external view returns (uint256 tokenId); + function getTokenIdForOriginal721(address originalAddress, uint256 originalTokenId) external view returns (uint256 tokenId); } \ No newline at end of file diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index e52bb3c..c7b5d80 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -133,7 +133,7 @@ interface IPatchworkProtocol { /** @notice Ran out of available IDs for allocation - @dev Max 255 IDs per NFT + @dev Max 255 IDs per target */ error OutOfIDs(); @@ -330,14 +330,13 @@ interface IPatchworkProtocol { function removeBanker(string memory scopeName, address addr) external; - // TODO nonreentrant function withdraw(string memory scopeName, uint256 amount) external; function balanceOf(string memory scopeName) external view returns (uint256 balance); - function mint(address to, address nft, bytes calldata data) external payable returns (uint256 tokenId); + function mint(address to, address mintable, bytes calldata data) external payable returns (uint256 tokenId); - function mintBatch(address to, address nft, bytes calldata data, uint256 quantity) external payable returns (uint256[] memory tokenIds); + function mintBatch(address to, address mintable, bytes calldata data, uint256 quantity) external payable returns (uint256[] memory tokenIds); function setProtocolFeeConfig(ProtocolFeeConfig memory config) external; @@ -374,8 +373,8 @@ interface IPatchworkProtocol { /** @notice Emitted when a patch is minted @param owner The owner of the patch - @param originalAddress The address of the original NFT's contract - @param originalTokenId The tokenId of the original NFT + @param originalAddress The address of the original 721's contract + @param originalTokenId The tokenId of the original 721 @param patchAddress The address of the patch's contract @param patchTokenId The tokenId of the patch */ @@ -384,8 +383,8 @@ interface IPatchworkProtocol { /** @notice Emitted when a patch is minted @param owner The owner of the patch - @param originalAddress The address of the original NFT's contract - @param originalTokenId The tokenId of the original NFT + @param originalAddress The address of the original 1155's contract + @param originalTokenId The tokenId of the original 1155 @param originalAccount The address of the original 1155's account @param patchAddress The address of the patch's contract @param patchTokenId The tokenId of the patch @@ -396,7 +395,7 @@ interface IPatchworkProtocol { /** @notice Emitted when an account patch is minted @param owner The owner of the patch - @param originalAddress The address of the original NFT's contract + @param originalAddress The address of the original account @param patchAddress The address of the patch's contract @param patchTokenId The tokenId of the patch */ @@ -478,10 +477,10 @@ interface IPatchworkProtocol { /** @notice Emitted when a mint is configured @param scopeName The name of the scope - @param nft The address of the NFT that is mintable + @param mintable The address of the IPatchworkMintable @param config The mint configuration */ - event MintConfigure(string scopeName, address indexed actor, address indexed nft, MintConfig config); + event MintConfigure(string scopeName, address indexed actor, address indexed mintable, MintConfig config); /** @notice Emitted when a banker is added to a scope @@ -531,23 +530,23 @@ interface IPatchworkProtocol { /** @notice Emitted on mint @param actor The address responsible for the action - @param scopeName The scope of the NFT + @param scopeName The scope of the IPatchworkMintable @param to The receipient of the mint - @param nft The nft minted + @param mintable The IPatchworkMintable minted @param data The data used to mint */ - event Mint(address indexed actor, string scopeName, address indexed to, address indexed nft, bytes data); + event Mint(address indexed actor, string scopeName, address indexed to, address indexed mintable, bytes data); /** @notice Emitted on batch mint @param actor The address responsible for the action - @param scopeName The scope of the NFT + @param scopeName The scope of the IPatchworkMintable @param to The receipient of the mint - @param nft The nft minted + @param mintable The IPatchworkMintable minted @param data The data used to mint @param quantity The quantity minted */ - event MintBatch(address indexed actor, string scopeName, address indexed to, address indexed nft, bytes data, uint256 quantity); + event MintBatch(address indexed actor, string scopeName, address indexed to, address indexed mintable, bytes data, uint256 quantity); /** @notice Claim a scope @@ -629,26 +628,22 @@ interface IPatchworkProtocol { /** @notice Create a new patch @param owner The owner of the patch - @param originalNFTAddress Address of the original NFT - @param originalNFTTokenId Token ID of the original NFT + @param originalAddress Address of the original 721 + @param originalTokenId Token ID of the original 721 @param patchAddress Address of the IPatchworkPatch to mint @return tokenId Token ID of the newly created patch */ - function createPatch(address owner, address originalNFTAddress, uint originalNFTTokenId, address patchAddress) external payable returns (uint256 tokenId); - - // patch, patch1155, patchAccount - // assign, assign, assignBatch, assignBatch (also rename in base NFTs?) - // mint, mintBatch + function patch(address owner, address originalAddress, uint originalTokenId, address patchAddress) external payable returns (uint256 tokenId); /** @notice Create a new 1155 patch - @param originalNFTAddress Address of the original NFT - @param originalNFTTokenId Token ID of the original NFT + @param originalAddress Address of the original 1155 + @param originalTokenId Token ID of the original 1155 @param originalAccount Address of the account to patch @param patchAddress Address of the IPatchworkPatch to mint @return tokenId Token ID of the newly created patch */ - function create1155Patch(address to, address originalNFTAddress, uint originalNFTTokenId, address originalAccount, address patchAddress) external payable returns (uint256 tokenId); + function patch1155(address to, address originalAddress, uint originalTokenId, address originalAccount, address patchAddress) external payable returns (uint256 tokenId); /** @notice Create a new account patch @@ -657,102 +652,102 @@ interface IPatchworkProtocol { @param patchAddress Address of the IPatchworkPatch to mint @return tokenId Token ID of the newly created patch */ - function createAccountPatch(address owner, address originalAddress, address patchAddress) external payable returns (uint256 tokenId); + function patchAccount(address owner, address originalAddress, address patchAddress) external payable returns (uint256 tokenId); /** - @notice Assigns an NFT relation to have an IPatchworkLiteRef form a LiteRef to a IPatchworkAssignableNFT - @param fragment The IPatchworkAssignableNFT address to assign - @param fragmentTokenId The IPatchworkAssignableNFT Token ID to assign + @notice Assigns a relation to have an IPatchworkLiteRef form a LiteRef to a IPatchworkAssignable + @param fragment The IPatchworkAssignable address to assign + @param fragmentTokenId The IPatchworkAssignable Token ID to assign @param target The IPatchworkLiteRef address to hold the reference to the fragment @param targetTokenId The IPatchworkLiteRef Token ID to hold the reference to the fragment */ - function assignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external payable; + function assign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external payable; /** - @notice Assigns an NFT relation to have an IPatchworkLiteRef form a LiteRef to a IPatchworkAssignableNFT - @param fragment The IPatchworkAssignableNFT address to assign - @param fragmentTokenId The IPatchworkAssignableNFT Token ID to assign + @notice Assigns a relation to have an IPatchworkLiteRef form a LiteRef to a IPatchworkAssignable + @param fragment The IPatchworkAssignable address to assign + @param fragmentTokenId The IPatchworkAssignable Token ID to assign @param target The IPatchworkLiteRef address to hold the reference to the fragment @param targetTokenId The IPatchworkLiteRef Token ID to hold the reference to the fragment - @param targetMetadataId The metadata ID on the target NFT to store the reference in + @param targetMetadataId The metadata ID on the target to store the reference in */ - function assignNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) external payable; + function assign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) external payable; /** - @notice Assign multiple NFT fragments to a target NFT in batch - @param fragments The array of addresses of the fragment IPatchworkAssignableNFTs - @param tokenIds The array of token IDs of the fragment IPatchworkAssignableNFTs - @param target The address of the target IPatchworkLiteRef NFT - @param targetTokenId The token ID of the target IPatchworkLiteRef NFT + @notice Assign multiple fragments to a target in batch + @param fragments The array of addresses of the fragment IPatchworkAssignables + @param tokenIds The array of token IDs of the fragment IPatchworkAssignables + @param target The address of the target IPatchworkLiteRef + @param targetTokenId The token ID of the target IPatchworkLiteRef */ - function batchAssignNFT(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) external payable; + function assignBatch(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) external payable; /** - @notice Assign multiple NFT fragments to a target NFT in batch - @param fragments The array of addresses of the fragment IPatchworkAssignableNFTs - @param tokenIds The array of token IDs of the fragment IPatchworkAssignableNFTs - @param target The address of the target IPatchworkLiteRef NFT - @param targetTokenId The token ID of the target IPatchworkLiteRef NFT - @param targetMetadataId The metadata ID on the target NFT to store the references in + @notice Assign multiple fragments to a target in batch + @param fragments The array of addresses of the fragment IPatchworkAssignables + @param tokenIds The array of token IDs of the fragment IPatchworkAssignables + @param target The address of the target IPatchworkLiteRef + @param targetTokenId The token ID of the target IPatchworkLiteRef + @param targetMetadataId The metadata ID on the target to store the references in */ - function batchAssignNFTDirect(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId, uint256 targetMetadataId) external payable; + function assignBatch(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId, uint256 targetMetadataId) external payable; /** - @notice Unassign a NFT fragment from a target NFT - @param fragment The IPatchworkSingleAssignableNFT address of the fragment NFT - @param fragmentTokenId The IPatchworkSingleAssignableNFT token ID of the fragment NFT - @dev reverts if fragment is not an IPatchworkSingleAssignableNFT + @notice Unassign a fragment from a target + @param fragment The IPatchworkSingleAssignable address of the fragment + @param fragmentTokenId The IPatchworkSingleAssignable token ID of the fragment + @dev reverts if fragment is not an IPatchworkSingleAssignable */ - function unassignSingleNFT(address fragment, uint fragmentTokenId) external; - + function unassignSingle(address fragment, uint fragmentTokenId) external; + /** - @notice Unassigns a multi NFT relation - @param fragment The IPatchworMultiAssignableNFT address to unassign - @param fragmentTokenId The IPatchworkMultiAssignableNFT Token ID to unassign - @param target The IPatchworkLiteRef address which holds a reference to the fragment - @param targetTokenId The IPatchworkLiteRef Token ID which holds a reference to the fragment - @dev reverts if fragment is not an IPatchworkMultiAssignableNFT + @notice Unassign a fragment from a target + @param fragment The IPatchworkSingleAssignable address of the fragment + @param fragmentTokenId The IPatchworkSingleAssignable token ID of the fragment + @param targetMetadataId The metadata ID on the target to unassign from + @dev reverts if fragment is not an IPatchworkSingleAssignable */ - function unassignMultiNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external; + function unassignSingle(address fragment, uint fragmentTokenId, uint256 targetMetadataId) external; /** - @notice Unassigns an NFT relation (single or multi) - @param fragment The IPatchworkAssignableNFT address to unassign - @param fragmentTokenId The IPatchworkAssignableNFT Token ID to unassign + @notice Unassigns a multi relation + @param fragment The IPatchworMultiAssignable address to unassign + @param fragmentTokenId The IPatchworkMultiAssignable Token ID to unassign @param target The IPatchworkLiteRef address which holds a reference to the fragment @param targetTokenId The IPatchworkLiteRef Token ID which holds a reference to the fragment + @dev reverts if fragment is not an IPatchworkMultiAssignable */ - function unassignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external; + function unassignMulti(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external; /** - @notice Unassign a NFT fragment from a target NFT - @param fragment The IPatchworkSingleAssignableNFT address of the fragment NFT - @param fragmentTokenId The IPatchworkSingleAssignableNFT token ID of the fragment NFT - @param targetMetadataId The metadata ID on the target NFT to unassign from - @dev reverts if fragment is not an IPatchworkSingleAssignableNFT + @notice Unassigns a multi relation + @param fragment The IPatchworMultiAssignable address to unassign + @param fragmentTokenId The IPatchworkMultiAssignable Token ID to unassign + @param target The IPatchworkLiteRef address which holds a reference to the fragment + @param targetTokenId The IPatchworkLiteRef Token ID which holds a reference to the fragment + @param targetMetadataId The metadata ID on the target to unassign from + @dev reverts if fragment is not an IPatchworkMultiAssignable */ - function unassignSingleNFTDirect(address fragment, uint fragmentTokenId, uint256 targetMetadataId) external; + function unassignMulti(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) external; /** - @notice Unassigns a multi NFT relation - @param fragment The IPatchworMultiAssignableNFT address to unassign - @param fragmentTokenId The IPatchworkMultiAssignableNFT Token ID to unassign + @notice Unassigns a relation (single or multi) + @param fragment The IPatchworkAssignable address to unassign + @param fragmentTokenId The IPatchworkAssignable Token ID to unassign @param target The IPatchworkLiteRef address which holds a reference to the fragment @param targetTokenId The IPatchworkLiteRef Token ID which holds a reference to the fragment - @param targetMetadataId The metadata ID on the target NFT to unassign from - @dev reverts if fragment is not an IPatchworkMultiAssignableNFT */ - function unassignMultiNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) external; + function unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) external; /** - @notice Unassigns an NFT relation (single or multi) - @param fragment The IPatchworkAssignableNFT address to unassign - @param fragmentTokenId The IPatchworkAssignableNFT Token ID to unassign + @notice Unassigns a relation (single or multi) + @param fragment The IPatchworkAssignable address to unassign + @param fragmentTokenId The IPatchworkAssignable Token ID to unassign @param target The IPatchworkLiteRef address which holds a reference to the fragment @param targetTokenId The IPatchworkLiteRef Token ID which holds a reference to the fragment - @param targetMetadataId The metadata ID on the target NFT to unassign from + @param targetMetadataId The metadata ID on the target to unassign from */ - function unassignNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) external; + function unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) external; /** @notice Apply transfer rules and actions of a specific token from one address to another @@ -763,9 +758,9 @@ interface IPatchworkProtocol { function applyTransfer(address from, address to, uint256 tokenId) external; /** - @notice Update the ownership tree of a specific Patchwork NFT - @param nft The address of the Patchwork NFT + @notice Update the ownership tree of a specific Patchwork 721 + @param addr The address of the Patchwork 721 @param tokenId The ID of the token whose ownership tree needs to be updated */ - function updateOwnershipTree(address nft, uint256 tokenId) external; + function updateOwnershipTree(address addr, uint256 tokenId) external; } \ No newline at end of file diff --git a/src/IPatchworkSingleAssignableNFT.sol b/src/IPatchworkSingleAssignable.sol similarity index 89% rename from src/IPatchworkSingleAssignableNFT.sol rename to src/IPatchworkSingleAssignable.sol index 98cb675..5d6ac99 100644 --- a/src/IPatchworkSingleAssignableNFT.sol +++ b/src/IPatchworkSingleAssignable.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "./IPatchworkAssignableNFT.sol"; +import "./IPatchworkAssignable.sol"; /** -@title Patchwork Protocol Assignable NFT Interface +@title Patchwork Protocol Assignable Interface @author Runic Labs, Inc @notice Interface for contracts supporting Patchwork assignment */ -interface IPatchworkSingleAssignableNFT is IPatchworkAssignableNFT { +interface IPatchworkSingleAssignable is IPatchworkAssignable { /** @notice Unassigns a token @param ourTokenId ID of our token diff --git a/src/Patchwork1155Patch.sol b/src/Patchwork1155Patch.sol index 111d735..d212e20 100644 --- a/src/Patchwork1155Patch.sol +++ b/src/Patchwork1155Patch.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "./PatchworkNFT.sol"; +import "./Patchwork721.sol"; import "./IPatchwork1155Patch.sol"; /** @title Patchwork1155Patch @dev Base implementation of IPatchwork1155Patch -@dev It extends the functionalities of PatchworkNFT and implements the IPatchwork1155Patch interface. +@dev It extends the functionalities of Patchwork721 and implements the IPatchwork1155Patch interface. */ -abstract contract Patchwork1155Patch is PatchworkNFT, IPatchwork1155Patch { +abstract contract Patchwork1155Patch is Patchwork721, IPatchwork1155Patch { struct PatchCanonical { address addr; @@ -32,32 +32,32 @@ abstract contract Patchwork1155Patch is PatchworkNFT, IPatchwork1155Patch { } /** - @dev See {IPatchworkNFT-getScopeName} + @dev See {IPatchwork721-getScopeName} */ - function getScopeName() public view virtual override(PatchworkNFT, IPatchworkScoped) returns (string memory) { + function getScopeName() public view virtual override(Patchwork721, IPatchworkScoped) returns (string memory) { return _scopeName; } /** @notice stores a patch @param tokenId the tokenId of the patch - @param originalNFTAddress the address of the original ERC-1155 we are patching - @param originalNFTTokenId the tokenId of the original ERC-1155 we are patching + @param originalAddress the address of the original ERC-1155 we are patching + @param originalTokenId the tokenId of the original ERC-1155 we are patching @param withReverse store reverse lookup @param account the account of the ERC-1155 we are patching */ - function _storePatch(uint256 tokenId, address originalNFTAddress, uint256 originalNFTTokenId, address account, bool withReverse) internal virtual { - _patchedAddresses[tokenId] = PatchCanonical(originalNFTAddress, originalNFTTokenId, account); + function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId, address account, bool withReverse) internal virtual { + _patchedAddresses[tokenId] = PatchCanonical(originalAddress, originalTokenId, account); if (withReverse) { - _patchedAddressesRev[keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId, account))] = tokenId; + _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId, account))] = tokenId; } } /** - @dev See {IPatchwork1155Patch-getTokenIdForOriginalNFT} + @dev See {IPatchwork1155Patch-getTokenIdForOriginal1155} */ - function getTokenIdForOriginalNFT(address originalNFTAddress, uint256 originalNFTTokenId, address originalAccount) public view virtual returns (uint256 tokenId) { - return _patchedAddressesRev[keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId, originalAccount))]; + function getTokenIdForOriginal1155(address originalAddress, uint256 originalTokenId, address originalAccount) public view virtual returns (uint256 tokenId) { + return _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId, originalAccount))]; } /** diff --git a/src/PatchworkNFT.sol b/src/Patchwork721.sol similarity index 82% rename from src/PatchworkNFT.sol rename to src/Patchwork721.sol index 735b28a..0f92696 100644 --- a/src/PatchworkNFT.sol +++ b/src/Patchwork721.sol @@ -2,46 +2,46 @@ pragma solidity ^0.8.13; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IPatchworkNFT.sol"; +import "./IPatchwork721.sol"; import "./IERC4906.sol"; import "./IPatchworkProtocol.sol"; /** -@title PatchworkNFT Abstract Contract -@dev This abstract contract defines the core functionalities for the PatchworkNFT. - It inherits from the standard ERC721, as well as the IPatchworkNFT and IERC4906 interfaces. +@title Patchwork721 Abstract Contract +@dev This abstract contract defines the core functionalities for the Patchwork721. + It inherits from the standard ERC721, as well as the IPatchwork721 and IERC4906 interfaces. */ -abstract contract PatchworkNFT is ERC721, IPatchworkNFT, IERC4906 { +abstract contract Patchwork721 is ERC721, IPatchwork721, IERC4906 { - /// @dev The scope name for the NFT. + /// @dev The scope name of this 721. string internal _scopeName; /// @dev The address that denotes the owner of the contract. address internal _owner; - /// @dev The address that manages the NFTs (PatchworkProtocol). + /// @dev Our manager (PatchworkProtocol). address internal _manager; /// @dev A mapping to keep track of permissions for each address. mapping(address => uint256) internal _permissionsAllow; - /// @dev A mapping for storing metadata associated with each NFT token ID. + /// @dev A mapping for storing metadata associated with each token ID. mapping(uint256 => uint256[]) internal _metadataStorage; - /// @dev A mapping for storing freeze nonces of each NFT token ID. + /// @dev A mapping for storing freeze nonces of each token ID. mapping(uint256 => uint256) internal _freezeNonces; - /// @dev A mapping indicating whether a specific NFT token ID is frozen. + /// @dev A mapping indicating whether a specific token ID is frozen. mapping(uint256 => bool) internal _freezes; - /// @dev A mapping indicating whether a specific NFT token ID is locked. + /// @dev A mapping indicating whether a specific token ID is locked. mapping(uint256 => bool) internal _locks; /** - * @notice Creates a new instance of the PatchworkNFT contract with the provided parameters. - * @param scopeName_ The scope name for the NFT. - * @param name_ The ERC-721 name for the NFT. - * @param symbol_ The ERC-721 symbol for the NFT. + * @notice Creates a new instance of the Patchwork721 contract with the provided parameters. + * @param scopeName_ The scope name. + * @param name_ The ERC-721 name. + * @param symbol_ The ERC-721 symbol. * @param owner_ The address that will be set as the owner. * @param manager_ The address that will be set as the manager (PatchworkProtocol). */ @@ -58,26 +58,40 @@ abstract contract PatchworkNFT is ERC721, IPatchworkNFT, IERC4906 { } /** - @dev See {IPatchworkNFT-getScopeName} + @dev See {IPatchwork721-getScopeName} */ function getScopeName() public view virtual returns (string memory) { return _scopeName; } /** - @dev See {IPatchworkNFT-storePackedMetadataSlot} + @dev See {IPatchwork721-storePackedMetadataSlot} */ function storePackedMetadataSlot(uint256 tokenId, uint256 slot, uint256 data) public virtual mustHaveTokenWriteAuth(tokenId) { _metadataStorage[tokenId][slot] = data; } /** - @dev See {IPatchworkNFT-loadPackedMetadataSlot} + @dev See {IPatchwork721-storePackedMetadata} + */ + function storePackedMetadata(uint256 tokenId, uint256[] memory data) public virtual mustHaveTokenWriteAuth(tokenId) { + _metadataStorage[tokenId] = data; + } + + /** + @dev See {IPatchwork721-loadPackedMetadataSlot} */ function loadPackedMetadataSlot(uint256 tokenId, uint256 slot) public virtual view returns (uint256) { return _metadataStorage[tokenId][slot]; } + /** + @dev See {IPatchwork721-loadPackedMetadata} + */ + function loadPackedMetadata(uint256 tokenId) public virtual view returns (uint256[] memory) { + return _metadataStorage[tokenId]; + } + // Does msg.sender have permission to write to our top level storage? function _checkWriteAuth() internal virtual view returns (bool allow) { return (msg.sender == _owner); @@ -89,7 +103,7 @@ abstract contract PatchworkNFT is ERC721, IPatchworkNFT, IERC4906 { } /** - @dev See {IPatchworkNFT-setPermissions} + @dev See {IPatchwork721-setPermissions} */ function setPermissions(address to, uint256 permissions) public virtual mustHaveWriteAuth { _permissionsAllow[to] = permissions; @@ -100,7 +114,7 @@ abstract contract PatchworkNFT is ERC721, IPatchworkNFT, IERC4906 { @dev See {IERC165-supportsInterface} */ function supportsInterface(bytes4 interfaceID) public view virtual override(ERC721, IERC165) returns (bool) { - return interfaceID == type(IPatchworkNFT).interfaceId || + return interfaceID == type(IPatchwork721).interfaceId || interfaceID == type(IERC5192).interfaceId || interfaceID == type(IERC4906).interfaceId || ERC721.supportsInterface(interfaceID); @@ -158,14 +172,14 @@ abstract contract PatchworkNFT is ERC721, IPatchworkNFT, IERC4906 { } /** - @dev See {IPatchworkNFT-getFreezeNonce} + @dev See {IPatchwork721-getFreezeNonce} */ function getFreezeNonce(uint256 tokenId) public view virtual returns (uint256 nonce) { return _freezeNonces[tokenId]; } /** - @dev See {IPatchworkNFT-setFrozen} + @dev See {IPatchwork721-setFrozen} */ function setFrozen(uint256 tokenId, bool frozen_) public virtual mustBeTokenOwner(tokenId) { bool _frozen = _freezes[tokenId]; @@ -182,21 +196,21 @@ abstract contract PatchworkNFT is ERC721, IPatchworkNFT, IERC4906 { } /** - @dev See {IPatchworkNFT-frozen} + @dev See {IPatchwork721-frozen} */ function frozen(uint256 tokenId) public view virtual returns (bool) { return _freezes[tokenId]; } /** - @dev See {IPatchworkNFT-locked} + @dev See {IPatchwork721-locked} */ function locked(uint256 tokenId) public view virtual returns (bool) { return _locks[tokenId]; } /** - @dev See {IPatchworkNFT-setLocked} + @dev See {IPatchwork721-setLocked} */ function setLocked(uint256 tokenId, bool locked_) public virtual mustBeTokenOwner(tokenId) { bool _locked = _locks[tokenId]; diff --git a/src/PatchworkAccountPatch.sol b/src/PatchworkAccountPatch.sol index a53b08b..cbc680c 100644 --- a/src/PatchworkAccountPatch.sol +++ b/src/PatchworkAccountPatch.sol @@ -3,14 +3,14 @@ pragma solidity ^0.8.13; import "./IPatchworkAccountPatch.sol"; import "./IPatchworkProtocol.sol"; -import "./PatchworkNFT.sol"; +import "./Patchwork721.sol"; /** @title PatchworkAccountPatch @dev Base implementation of IPatchworkAccountPatch -@dev It extends the functionalities of PatchworkNFT and implements the IPatchworkAccountPatch interface. +@dev It extends the functionalities of Patchwork721 and implements the IPatchworkAccountPatch interface. */ -abstract contract PatchworkAccountPatch is PatchworkNFT, IPatchworkAccountPatch { +abstract contract PatchworkAccountPatch is Patchwork721, IPatchworkAccountPatch { /// @dev Mapping from token ID to the address of the NFT that this patch is applied to. mapping(uint256 => address) internal _patchedAddresses; @@ -27,9 +27,9 @@ abstract contract PatchworkAccountPatch is PatchworkNFT, IPatchworkAccountPatch } /** - @dev See {IPatchworkNFT-getScopeName} + @dev See {IPatchwork721-getScopeName} */ - function getScopeName() public view virtual override(PatchworkNFT, IPatchworkScoped) returns (string memory) { + function getScopeName() public view virtual override(Patchwork721, IPatchworkScoped) returns (string memory) { return _scopeName; } diff --git a/src/PatchworkFragmentMulti.sol b/src/PatchworkFragmentMulti.sol index 5f42787..0b94a27 100644 --- a/src/PatchworkFragmentMulti.sol +++ b/src/PatchworkFragmentMulti.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "./PatchworkNFT.sol"; -import "./IPatchworkMultiAssignableNFT.sol"; +import "./Patchwork721.sol"; +import "./IPatchworkMultiAssignable.sol"; /** @title PatchworkFragmentMulti -@dev base implementation of a Single-relation Fragment is IPatchworkAssignableNFT +@dev base implementation of a Single-relation Fragment is IPatchworkAssignable */ -abstract contract PatchworkFragmentMulti is PatchworkNFT, IPatchworkMultiAssignableNFT { +abstract contract PatchworkFragmentMulti is Patchwork721, IPatchworkMultiAssignable { struct AssignmentStorage { mapping(bytes32 => uint256) index; @@ -21,9 +21,9 @@ abstract contract PatchworkFragmentMulti is PatchworkNFT, IPatchworkMultiAssigna mapping(uint256 => AssignmentStorage) internal _assignmentStorage; /** - @dev See {IPatchworkNFT-getScopeName} + @dev See {IPatchwork721-getScopeName} */ - function getScopeName() public view virtual override (PatchworkNFT, IPatchworkScoped) returns (string memory) { + function getScopeName() public view virtual override (Patchwork721, IPatchworkScoped) returns (string memory) { return _scopeName; } @@ -31,8 +31,8 @@ abstract contract PatchworkFragmentMulti is PatchworkNFT, IPatchworkMultiAssigna @dev See {IERC165-supportsInterface} */ function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { - return interfaceID == type(IPatchworkAssignableNFT).interfaceId || - interfaceID == type(IPatchworkMultiAssignableNFT).interfaceId || + return interfaceID == type(IPatchworkAssignable).interfaceId || + interfaceID == type(IPatchworkMultiAssignable).interfaceId || super.supportsInterface(interfaceID); } @@ -98,14 +98,14 @@ abstract contract PatchworkFragmentMulti is PatchworkNFT, IPatchworkMultiAssigna /** - @dev See {IPatchworkNFT-getAssignmentCount} + @dev See {IPatchwork721-getAssignmentCount} */ function getAssignmentCount(uint256 tokenId) public view returns (uint256) { return _assignmentStorage[tokenId].assignments.length; } /** - @dev See {IPatchworkNFT-getAssignments} + @dev See {IPatchwork721-getAssignments} */ function getAssignments(uint256 tokenId, uint256 offset, uint256 count) external view returns (Assignment[] memory) { AssignmentStorage storage store = _assignmentStorage[tokenId]; diff --git a/src/PatchworkFragmentSingle.sol b/src/PatchworkFragmentSingle.sol index bacd023..f6d908e 100644 --- a/src/PatchworkFragmentSingle.sol +++ b/src/PatchworkFragmentSingle.sol @@ -1,17 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "./PatchworkNFT.sol"; -import "./IPatchworkSingleAssignableNFT.sol"; - -// TODO create a patch fragment implementation where ownership is weak for fragments -// TODO change the patchworkCompatible to make that work +import "./Patchwork721.sol"; +import "./IPatchworkSingleAssignable.sol"; /** @title PatchworkFragmentSingle -@dev base implementation of a Single-relation Fragment is IPatchworkSingleAssignableNFT +@dev base implementation of a Single-relation Fragment is IPatchworkSingleAssignable */ -abstract contract PatchworkFragmentSingle is PatchworkNFT, IPatchworkSingleAssignableNFT { +abstract contract PatchworkFragmentSingle is Patchwork721, IPatchworkSingleAssignable { /// Represents an assignment of a token from an external NFT contract to a token in this contract. struct Assignment { @@ -23,9 +20,9 @@ abstract contract PatchworkFragmentSingle is PatchworkNFT, IPatchworkSingleAssig mapping(uint256 => Assignment) internal _assignments; /** - @dev See {IPatchworkNFT-getScopeName} + @dev See {IPatchwork721-getScopeName} */ - function getScopeName() public view virtual override (PatchworkNFT, IPatchworkScoped) returns (string memory) { + function getScopeName() public view virtual override (Patchwork721, IPatchworkScoped) returns (string memory) { return _scopeName; } @@ -33,8 +30,8 @@ abstract contract PatchworkFragmentSingle is PatchworkNFT, IPatchworkSingleAssig @dev See {IERC165-supportsInterface} */ function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { - return interfaceID == type(IPatchworkAssignableNFT).interfaceId || - interfaceID == type(IPatchworkSingleAssignableNFT).interfaceId || + return interfaceID == type(IPatchworkAssignable).interfaceId || + interfaceID == type(IPatchworkSingleAssignable).interfaceId || super.supportsInterface(interfaceID); } @@ -122,7 +119,7 @@ abstract contract PatchworkFragmentSingle is PatchworkNFT, IPatchworkSingleAssig } /** - @dev See {IPatchworkNFT-locked} + @dev See {IPatchwork721-locked} */ function locked(uint256 tokenId) public view virtual override returns (bool) { // Locked when assigned (implicit) or if explicitly locked @@ -130,7 +127,7 @@ abstract contract PatchworkFragmentSingle is PatchworkNFT, IPatchworkSingleAssig } /** - @dev See {IPatchworkNFT-setLocked} + @dev See {IPatchwork721-setLocked} */ function setLocked(uint256 tokenId, bool locked_) public virtual override { if (msg.sender != ownerOf(tokenId)) { diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index 4d960cd..3398433 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "./PatchworkNFT.sol"; +import "./Patchwork721.sol"; import "./IPatchworkPatch.sol"; /** @title PatchworkPatch @dev Base implementation of IPatchworkPatch @dev It is soul-bound to another ERC-721 and cannot be transferred or reassigned. -@dev It extends the functionalities of PatchworkNFT and implements the IPatchworkPatch interface. +@dev It extends the functionalities of Patchwork721 and implements the IPatchworkPatch interface. */ -abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { +abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { /// @dev Mapping from token ID to the address of the NFT that this patch is applied to. mapping(uint256 => address) internal _patchedAddresses; @@ -30,9 +30,9 @@ abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { } /** - @dev See {IPatchworkNFT-getScopeName} + @dev See {IPatchwork721-getScopeName} */ - function getScopeName() public view virtual override(PatchworkNFT, IPatchworkScoped) returns (string memory) { + function getScopeName() public view virtual override(Patchwork721, IPatchworkScoped) returns (string memory) { return _scopeName; } @@ -48,23 +48,23 @@ abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { /** @notice stores a patch @param tokenId the tokenId of the patch - @param originalNFTAddress the address of the original ERC-721 we are patching - @param originalNFTTokenId the tokenId of the original ERC-721 we are patching + @param originalAddress the address of the original ERC-721 we are patching + @param originalTokenId the tokenId of the original ERC-721 we are patching @param withReverse store reverse lookup */ - function _storePatch(uint256 tokenId, address originalNFTAddress, uint256 originalNFTTokenId, bool withReverse) internal virtual { - _patchedAddresses[tokenId] = originalNFTAddress; - _patchedTokenIds[tokenId] = originalNFTTokenId; + function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId, bool withReverse) internal virtual { + _patchedAddresses[tokenId] = originalAddress; + _patchedTokenIds[tokenId] = originalTokenId; if (withReverse) { - _patchedAddressesRev[keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId))] = tokenId; + _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))] = tokenId; } } /** - @dev See {IPatchworkPatch-getTokenIdForOriginalNFT} + @dev See {IPatchworkPatch-getTokenIdForOriginal721} */ - function getTokenIdForOriginalNFT(address originalNFTAddress, uint256 originalNFTTokenId) public view virtual returns (uint256 tokenId) { - return _patchedAddressesRev[keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId))]; + function getTokenIdForOriginal721(address originalAddress, uint256 originalTokenId) public view virtual returns (uint256 tokenId) { + return _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))]; } /** @@ -91,7 +91,7 @@ abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { /** @dev always false because a patch cannot be locked as the ownership is inferred - @dev See {IPatchworkNFT-locked} + @dev See {IPatchwork721-locked} */ function locked(uint256 /* tokenId */) public pure virtual override returns (bool) { return false; @@ -99,7 +99,7 @@ abstract contract PatchworkPatch is PatchworkNFT, IPatchworkPatch { /** @dev always reverts because a patch cannot be locked as the ownership is inferred - @dev See {IPatchworkNFT-setLocked} + @dev See {IPatchwork721-setLocked} */ function setLocked(uint256 /* tokenId */, bool /* locked_ */) public view virtual override { revert IPatchworkProtocol.CannotLockSoulboundPatch(address(this)); diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index ee1b276..89a6c54 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "./IPatchworkNFT.sol"; -import "./IPatchworkSingleAssignableNFT.sol"; -import "./IPatchworkMultiAssignableNFT.sol"; +import "./IPatchwork721.sol"; +import "./IPatchworkSingleAssignable.sol"; +import "./IPatchworkMultiAssignable.sol"; import "./IPatchworkLiteRef.sol"; import "./IPatchworkPatch.sol"; import "./IPatchwork1155Patch.sol"; @@ -18,7 +18,6 @@ import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; /** @title Patchwork Protocol @author Runic Labs, Inc -@notice Manages data integrity of relational NFTs implemented with Patchwork interfaces */ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @@ -232,7 +231,8 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { // modify state before calling to send scope.balance -= amount; // transfer funds - (bool sent,) = msg.sender.call{value: amount, gas: TRANSFER_GAS_LIMIT}(""); + // (bool sent,) = msg.sender.call{value: amount, gas: TRANSFER_GAS_LIMIT}(""); // TODO is gas limit good or bad? + (bool sent,) = msg.sender.call{value: amount}(""); if (!sent) { revert FailedToSend(); } @@ -244,35 +244,35 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { return scope.balance; } - function mint(address to, address nft, bytes calldata data) external payable returns (uint256 tokenId) { - (MintConfig memory config, string memory scopeName, Scope storage scope) = _setupMint(nft); + function mint(address to, address mintable, bytes calldata data) external payable returns (uint256 tokenId) { + (MintConfig memory config, string memory scopeName, Scope storage scope) = _setupMint(mintable); if (msg.value != config.flatFee) { revert IncorrectFeeAmount(); } _handleMintFee(scope); - tokenId = IPatchworkMintable(nft).mint(to, data); - emit Mint(msg.sender, scopeName, to, nft, data); + tokenId = IPatchworkMintable(mintable).mint(to, data); + emit Mint(msg.sender, scopeName, to, mintable, data); } - function mintBatch(address to, address nft, bytes calldata data, uint256 quantity) external payable returns (uint256[] memory tokenIds) { - (MintConfig memory config, string memory scopeName, Scope storage scope) = _setupMint(nft); + function mintBatch(address to, address mintable, bytes calldata data, uint256 quantity) external payable returns (uint256[] memory tokenIds) { + (MintConfig memory config, string memory scopeName, Scope storage scope) = _setupMint(mintable); uint256 totalFee = config.flatFee * quantity; if (msg.value != totalFee) { revert IncorrectFeeAmount(); } _handleMintFee(scope); - tokenIds = IPatchworkMintable(nft).mintBatch(to, data, quantity); - emit MintBatch(msg.sender, scopeName, to, nft, data, quantity); + tokenIds = IPatchworkMintable(mintable).mintBatch(to, data, quantity); + emit MintBatch(msg.sender, scopeName, to, mintable, data, quantity); } - function _setupMint(address nft) internal view returns (MintConfig memory config, string memory scopeName, Scope storage scope) { - if (!IERC165(nft).supportsInterface(type(IPatchworkMintable).interfaceId)) { + function _setupMint(address mintable) internal view returns (MintConfig memory config, string memory scopeName, Scope storage scope) { + if (!IERC165(mintable).supportsInterface(type(IPatchworkMintable).interfaceId)) { revert UnsupportedContract(); } - scopeName = IPatchworkMintable(nft).getScopeName(); + scopeName = IPatchworkMintable(mintable).getScopeName(); scope = _mustHaveScope(scopeName); - _mustBeWhitelisted(scopeName, scope, nft); - config = scope.mintConfigurations[nft]; + _mustBeWhitelisted(scopeName, scope, mintable); + config = scope.mintConfigurations[mintable]; if (!config.active) { revert MintNotActive(); } @@ -316,7 +316,8 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { revert InsufficientFunds(); } _protocolBalance -= amount; - (bool sent,) = msg.sender.call{value: amount, gas: TRANSFER_GAS_LIMIT}(""); + // (bool sent,) = msg.sender.call{value: amount, gas: TRANSFER_GAS_LIMIT}(""); // TODO is gas limit good or bad? + (bool sent,) = msg.sender.call{value: amount}(""); if (!sent) { revert FailedToSend(); } @@ -348,12 +349,11 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } /** - @dev See {IPatchworkProtocol-createPatch} + @dev See {IPatchworkProtocol-patch} */ - function createPatch(address owner, address originalNFTAddress, uint originalNFTTokenId, address patchAddress) external payable returns (uint256 tokenId) { - IPatchworkPatch patch = IPatchworkPatch(patchAddress); - string memory scopeName = patch.getScopeName(); - // mint a Patch that is soulbound to the originalNFT using the contract address at patchAddress which must support Patchwork metadata + function patch(address owner, address originalAddress, uint originalTokenId, address patchAddress) external payable returns (uint256 tokenId) { + IPatchworkPatch patch_ = IPatchworkPatch(patchAddress); + string memory scopeName = patch_.getScopeName(); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, patchAddress); if (scope.owner == msg.sender || scope.operators[msg.sender]) { @@ -364,24 +364,23 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { revert NotAuthorized(msg.sender); } _handlePatchFee(scope, patchAddress); - // limit this to one unique patch (originalNFTAddress+TokenID+patchAddress) - bytes32 _hash = keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId, patchAddress)); + // limit this to one unique patch (originalAddress+TokenID+patchAddress) + bytes32 _hash = keccak256(abi.encodePacked(originalAddress, originalTokenId, patchAddress)); if (_uniquePatches[_hash]) { - revert AlreadyPatched(originalNFTAddress, originalNFTTokenId, patchAddress); + revert AlreadyPatched(originalAddress, originalTokenId, patchAddress); } _uniquePatches[_hash] = true; - tokenId = patch.mintPatch(owner, originalNFTAddress, originalNFTTokenId); - emit Patch(owner, originalNFTAddress, originalNFTTokenId, patchAddress, tokenId); + tokenId = patch_.mintPatch(owner, originalAddress, originalTokenId); + emit Patch(owner, originalAddress, originalTokenId, patchAddress, tokenId); return tokenId; } /** - @dev See {IPatchworkProtocol-create1155Patch} + @dev See {IPatchworkProtocol-patch1155} */ - function create1155Patch(address to, address originalNFTAddress, uint originalNFTTokenId, address originalAccount, address patchAddress) external payable returns (uint256 tokenId) { - IPatchwork1155Patch patch = IPatchwork1155Patch(patchAddress); - string memory scopeName = patch.getScopeName(); - // mint a Patch that is soulbound to the originalNFT using the contract address at patchAddress which must support Patchwork metadata + function patch1155(address to, address originalAddress, uint originalTokenId, address originalAccount, address patchAddress) external payable returns (uint256 tokenId) { + IPatchwork1155Patch patch_ = IPatchwork1155Patch(patchAddress); + string memory scopeName = patch_.getScopeName(); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, patchAddress); if (scope.owner == msg.sender || scope.operators[msg.sender]) { @@ -392,23 +391,22 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { revert NotAuthorized(msg.sender); } _handlePatchFee(scope, patchAddress); - // limit this to one unique patch (originalNFTAddress+TokenID+patchAddress) - bytes32 _hash = keccak256(abi.encodePacked(originalNFTAddress, originalNFTTokenId, originalAccount, patchAddress)); + // limit this to one unique patch (originalAddress+TokenID+patchAddress) + bytes32 _hash = keccak256(abi.encodePacked(originalAddress, originalTokenId, originalAccount, patchAddress)); if (_uniquePatches[_hash]) { - revert ERC1155AlreadyPatched(originalNFTAddress, originalNFTTokenId, originalAccount, patchAddress); + revert ERC1155AlreadyPatched(originalAddress, originalTokenId, originalAccount, patchAddress); } _uniquePatches[_hash] = true; - tokenId = patch.mintPatch(to, originalNFTAddress, originalNFTTokenId, originalAccount); - emit ERC1155Patch(to, originalNFTAddress, originalNFTTokenId, originalAccount, patchAddress, tokenId); + tokenId = patch_.mintPatch(to, originalAddress, originalTokenId, originalAccount); + emit ERC1155Patch(to, originalAddress, originalTokenId, originalAccount, patchAddress, tokenId); return tokenId; } /** - @dev See {IPatchworkProtocol-createAccountPatch} + @dev See {IPatchworkProtocol-patchAccount} */ - function createAccountPatch(address owner, address originalAddress, address patchAddress) external payable returns (uint256 tokenId) { - IPatchworkAccountPatch patch = IPatchworkAccountPatch(patchAddress); - string memory scopeName = patch.getScopeName(); - // mint a Patch that is soulbound to the originalNFT using the contract address at patchAddress which must support Patchwork metadata + function patchAccount(address owner, address originalAddress, address patchAddress) external payable returns (uint256 tokenId) { + IPatchworkAccountPatch patch_ = IPatchworkAccountPatch(patchAddress); + string memory scopeName = patch_.getScopeName(); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, patchAddress); if (scope.owner == msg.sender || scope.operators[msg.sender]) { @@ -425,7 +423,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { revert AccountAlreadyPatched(originalAddress, patchAddress); } _uniquePatches[_hash] = true; - tokenId = patch.mintPatch(owner, originalAddress); + tokenId = patch_.mintPatch(owner, originalAddress); emit AccountPatch(owner, originalAddress, patchAddress, tokenId); return tokenId; } @@ -455,41 +453,41 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } /** - @dev See {IPatchworkProtocol-assignNFT} + @dev See {IPatchworkProtocol-assign} */ - function assignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public payable mustNotBeFrozen(target, targetTokenId) { + function assign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public payable mustNotBeFrozen(target, targetTokenId) { address targetOwner = IERC721(target).ownerOf(targetTokenId); uint64 ref = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); IPatchworkLiteRef(target).addReference(targetTokenId, ref); } /** - @dev See {IPatchworkProtocol-assignNFTDirect} + @dev See {IPatchworkProtocol-assign} */ - function assignNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public payable mustNotBeFrozen(target, targetTokenId) { + function assign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public payable mustNotBeFrozen(target, targetTokenId) { address targetOwner = IERC721(target).ownerOf(targetTokenId); uint64 ref = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); - IPatchworkLiteRef(target).addReferenceDirect(targetTokenId, ref, targetMetadataId); + IPatchworkLiteRef(target).addReference(targetTokenId, ref, targetMetadataId); } /** - @dev See {IPatchworkProtocol-batchAssignNFT} + @dev See {IPatchworkProtocol-assignBatch} */ - function batchAssignNFT(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) public payable mustNotBeFrozen(target, targetTokenId) { + function assignBatch(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) public payable mustNotBeFrozen(target, targetTokenId) { (uint64[] memory refs, ) = _batchAssignCommon(fragments, tokenIds, target, targetTokenId); - IPatchworkLiteRef(target).batchAddReferences(targetTokenId, refs); + IPatchworkLiteRef(target).addReferenceBatch(targetTokenId, refs); } /** - @dev See {IPatchworkProtocol-batchAssignNFTDirect} + @dev See {IPatchworkProtocol-assignBatch} */ - function batchAssignNFTDirect(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId, uint256 targetMetadataId) public payable mustNotBeFrozen(target, targetTokenId) { + function assignBatch(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId, uint256 targetMetadataId) public payable mustNotBeFrozen(target, targetTokenId) { (uint64[] memory refs, ) = _batchAssignCommon(fragments, tokenIds, target, targetTokenId); - IPatchworkLiteRef(target).batchAddReferencesDirect(targetTokenId, refs, targetMetadataId); + IPatchworkLiteRef(target).addReferenceBatch(targetTokenId, refs, targetMetadataId); } /** - @dev Common function to handle the batch assignment of NFTs. + @dev Common function to handle the batch assignments. */ function _batchAssignCommon(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) private returns (uint64[] memory refs, address targetOwner) { if (fragments.length != tokenIds.length) { @@ -505,9 +503,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } /** - @notice Performs assignment of an IPatchworkAssignableNFT to an IPatchworkLiteRef - @param fragment the IPatchworkAssignableNFT's address - @param fragmentTokenId the IPatchworkAssignableNFT's tokenId + @notice Performs assignment of an IPatchworkAssignable to an IPatchworkLiteRef + @param fragment the IPatchworkAssignable's address + @param fragmentTokenId the IPatchworkAssignable's tokenId @param target the IPatchworkLiteRef target's address @param targetTokenId the IPatchworkLiteRef target's tokenId @param targetOwner the owner address of the target @@ -517,17 +515,17 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { if (fragment == target && fragmentTokenId == targetTokenId) { revert SelfAssignmentNotAllowed(fragment, fragmentTokenId); } - IPatchworkAssignableNFT assignableNFT = IPatchworkAssignableNFT(fragment); + IPatchworkAssignable assignable = IPatchworkAssignable(fragment); if (_isLocked(fragment, fragmentTokenId)) { revert Locked(fragment, fragmentTokenId); } // Use the target's scope for general permission and check the fragment for detailed permissions - string memory targetScopeName = IPatchworkNFT(target).getScopeName(); + string memory targetScopeName = IPatchwork721(target).getScopeName(); Scope storage targetScope = _mustHaveScope(targetScopeName); _mustBeWhitelisted(targetScopeName, targetScope, target); { // Whitelist check, these variables do not need to stay in the function level stack - string memory fragmentScopeName = assignableNFT.getScopeName(); + string memory fragmentScopeName = assignable.getScopeName(); Scope storage fragmentScope = _mustHaveScope(fragmentScopeName); _mustBeWhitelisted(fragmentScopeName, fragmentScope, fragment); _handleAssignFee(fragmentScope, fragment); @@ -542,7 +540,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { revert NotAuthorized(msg.sender); } - if (!IPatchworkAssignableNFT(fragment).allowAssignment(fragmentTokenId, target, targetTokenId, targetOwner, msg.sender, targetScopeName)) { + if (!IPatchworkAssignable(fragment).allowAssignment(fragmentTokenId, target, targetTokenId, targetOwner, msg.sender, targetScopeName)) { revert NotAuthorized(msg.sender); } bytes32 targetRef; @@ -564,7 +562,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { revert FragmentAlreadyAssigned(address(_fragment), _fragmentTokenId); } // call assign on the fragment - assignableNFT.assign(_fragmentTokenId, _target, _targetTokenId); + assignable.assign(_fragmentTokenId, _target, _targetTokenId); // add to our storage of assignments _liteRefs[targetRef] = true; emit Assign(targetOwner, _fragment, _fragmentTokenId, _target, _targetTokenId); @@ -572,38 +570,38 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } /** - @dev See {IPatchworkProtocol-unassignNFT} + @dev See {IPatchworkProtocol-unassign} */ - function unassignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { - _unassignNFT(fragment, fragmentTokenId, target, targetTokenId, false, 0); + function unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { + _unassign(fragment, fragmentTokenId, target, targetTokenId, false, 0); } /** - @dev See {IPatchworkProtocol-unassignNFTDirect} + @dev See {IPatchworkProtocol-unassign} */ - function unassignNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { - _unassignNFT(fragment, fragmentTokenId, target, targetTokenId, true, targetMetadataId); + function unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { + _unassign(fragment, fragmentTokenId, target, targetTokenId, true, targetMetadataId); } /** - @dev Common function to handle the unassignment of NFTs. + @dev Common function to handle unassignments. */ - function _unassignNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool isDirect, uint256 targetMetadataId) private { - if (IERC165(fragment).supportsInterface(type(IPatchworkMultiAssignableNFT).interfaceId)) { + function _unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool isDirect, uint256 targetMetadataId) private { + if (IERC165(fragment).supportsInterface(type(IPatchworkMultiAssignable).interfaceId)) { if (isDirect) { - unassignMultiNFTDirect(fragment, fragmentTokenId, target, targetTokenId, targetMetadataId); + unassignMulti(fragment, fragmentTokenId, target, targetTokenId, targetMetadataId); } else { - unassignMultiNFT(fragment, fragmentTokenId, target, targetTokenId); + unassignMulti(fragment, fragmentTokenId, target, targetTokenId); } - } else if (IERC165(fragment).supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)) { - (address _target, uint256 _targetTokenId) = IPatchworkSingleAssignableNFT(fragment).getAssignedTo(fragmentTokenId); + } else if (IERC165(fragment).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { + (address _target, uint256 _targetTokenId) = IPatchworkSingleAssignable(fragment).getAssignedTo(fragmentTokenId); if (target != _target || _targetTokenId != targetTokenId) { revert FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); } if (isDirect) { - unassignSingleNFTDirect(fragment, fragmentTokenId, targetMetadataId); + unassignSingle(fragment, fragmentTokenId, targetMetadataId); } else { - unassignSingleNFT(fragment, fragmentTokenId); + unassignSingle(fragment, fragmentTokenId); } } else { revert UnsupportedContract(); @@ -611,24 +609,24 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } /** - @dev See {IPatchworkProtocol-unassignMultiNFT} + @dev See {IPatchworkProtocol-unassignMulti} */ - function unassignMultiNFT(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { + function unassignMulti(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { _unassignMultiCommon(fragment, fragmentTokenId, target, targetTokenId, false, 0); } /** - @dev See {IPatchworkProtocol-unassignMultiNFTDirect} + @dev See {IPatchworkProtocol-unassignMulti} */ - function unassignMultiNFTDirect(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { + function unassignMulti(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { _unassignMultiCommon(fragment, fragmentTokenId, target, targetTokenId, true, targetMetadataId); } /** - @dev Common function to handle the unassignment of multi NFTs. + @dev Common function to handle the unassignment of multi assignables. */ function _unassignMultiCommon(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool isDirect, uint256 targetMetadataId) private { - IPatchworkMultiAssignableNFT assignable = IPatchworkMultiAssignableNFT(fragment); + IPatchworkMultiAssignable assignable = IPatchworkMultiAssignable(fragment); string memory scopeName = assignable.getScopeName(); if (!assignable.isAssignedTo(fragmentTokenId, target, targetTokenId)) { revert FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); @@ -638,37 +636,37 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } /** - @dev See {IPatchworkProtocol-unassignSingleNFT} + @dev See {IPatchworkProtocol-unassignSingle} */ - function unassignSingleNFT(address fragment, uint fragmentTokenId) public mustNotBeFrozen(fragment, fragmentTokenId) { + function unassignSingle(address fragment, uint fragmentTokenId) public mustNotBeFrozen(fragment, fragmentTokenId) { _unassignSingleCommon(fragment, fragmentTokenId, false, 0); } /** - @dev See {IPatchworkProtocol-unassignSingleNFTDirect} + @dev See {IPatchworkProtocol-unassignSingle} */ - function unassignSingleNFTDirect(address fragment, uint fragmentTokenId, uint256 targetMetadataId) public mustNotBeFrozen(fragment, fragmentTokenId) { + function unassignSingle(address fragment, uint fragmentTokenId, uint256 targetMetadataId) public mustNotBeFrozen(fragment, fragmentTokenId) { _unassignSingleCommon(fragment, fragmentTokenId, true, targetMetadataId); } /** - @dev Common function to handle the unassignment of single NFTs. + @dev Common function to handle the unassignment of single assignables. */ function _unassignSingleCommon(address fragment, uint fragmentTokenId, bool isDirect, uint256 targetMetadataId) private { - IPatchworkSingleAssignableNFT assignableNFT = IPatchworkSingleAssignableNFT(fragment); - string memory scopeName = assignableNFT.getScopeName(); - (address target, uint256 targetTokenId) = assignableNFT.getAssignedTo(fragmentTokenId); + IPatchworkSingleAssignable assignable = IPatchworkSingleAssignable(fragment); + string memory scopeName = assignable.getScopeName(); + (address target, uint256 targetTokenId) = assignable.getAssignedTo(fragmentTokenId); if (target == address(0)) { revert FragmentNotAssigned(fragment, fragmentTokenId); } _doUnassign(fragment, fragmentTokenId, target, targetTokenId, isDirect, targetMetadataId, scopeName); - assignableNFT.unassign(fragmentTokenId); + assignable.unassign(fragmentTokenId); } /** - @notice Performs unassignment of an IPatchworkAssignableNFT to an IPatchworkLiteRef - @param fragment the IPatchworkAssignableNFT's address - @param fragmentTokenId the IPatchworkAssignableNFT's tokenId + @notice Performs unassignment of an IPatchworkAssignable to an IPatchworkLiteRef + @param fragment the IPatchworkAssignable's address + @param fragmentTokenId the IPatchworkAssignable's tokenId @param target the IPatchworkLiteRef target's address @param targetTokenId the IPatchworkLiteRef target's tokenId @param scopeName the name of the assignable's scope @@ -695,7 +693,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } delete _liteRefs[targetRef]; if (direct) { - IPatchworkLiteRef(target).removeReferenceDirect(targetTokenId, ref, targetMetadataId); + IPatchworkLiteRef(target).removeReference(targetTokenId, ref, targetMetadataId); } else { IPatchworkLiteRef(target).removeReference(targetTokenId, ref); } @@ -708,9 +706,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { */ function applyTransfer(address from, address to, uint256 tokenId) public { address nft = msg.sender; - if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)) { - IPatchworkSingleAssignableNFT assignableNFT = IPatchworkSingleAssignableNFT(nft); - (address addr,) = assignableNFT.getAssignedTo(tokenId); + if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { + IPatchworkSingleAssignable assignable = IPatchworkSingleAssignable(nft); + (address addr,) = assignable.getAssignedTo(tokenId); if (addr != address(0)) { revert TransferBlockedByAssignment(nft, tokenId); } @@ -718,14 +716,13 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { if (IERC165(nft).supportsInterface(type(IPatchworkPatch).interfaceId)) { revert TransferNotAllowed(nft, tokenId); } - if (IERC165(nft).supportsInterface(type(IPatchworkNFT).interfaceId)) { - if (IPatchworkNFT(nft).locked(tokenId)) { + if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { + if (IPatchwork721(nft).locked(tokenId)) { revert Locked(nft, tokenId); } } if (IERC165(nft).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { - IPatchworkLiteRef liteRefNFT = IPatchworkLiteRef(nft); - (address[] memory addresses, uint256[] memory tokenIds) = liteRefNFT.loadAllStaticReferences(tokenId); + (address[] memory addresses, uint256[] memory tokenIds) = IPatchworkLiteRef(nft).loadAllStaticReferences(tokenId); for (uint i = 0; i < addresses.length; i++) { if (addresses[i] != address(0)) { _applyAssignedTransfer(addresses[i], from, to, tokenIds[i], nft, tokenId); @@ -734,20 +731,19 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } } - function _applyAssignedTransfer(address nft, address from, address to, uint256 tokenId, address assignedToNFT_, uint256 assignedToTokenId_) private { - if (!IERC165(nft).supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)) { + function _applyAssignedTransfer(address nft, address from, address to, uint256 tokenId, address assignedTo_, uint256 assignedToTokenId_) private { + if (!IERC165(nft).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { revert NotPatchworkAssignable(nft); } - (address assignedToNFT, uint256 assignedToTokenId) = IPatchworkSingleAssignableNFT(nft).getAssignedTo(tokenId); + (address assignedTo, uint256 assignedToTokenId) = IPatchworkSingleAssignable(nft).getAssignedTo(tokenId); // 2-way Check the assignment to prevent spoofing - if (assignedToNFT_ != assignedToNFT || assignedToTokenId_ != assignedToTokenId) { - revert DataIntegrityError(assignedToNFT_, assignedToTokenId_, assignedToNFT, assignedToTokenId); + if (assignedTo_ != assignedTo || assignedToTokenId_ != assignedToTokenId) { + revert DataIntegrityError(assignedTo_, assignedToTokenId_, assignedTo, assignedToTokenId); } - IPatchworkSingleAssignableNFT(nft).onAssignedTransfer(from, to, tokenId); + IPatchworkSingleAssignable(nft).onAssignedTransfer(from, to, tokenId); if (IERC165(nft).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { address nft_ = nft; // local variable prevents optimizer stack issue in v0.8.18 - IPatchworkLiteRef liteRefNFT = IPatchworkLiteRef(nft); - (address[] memory addresses, uint256[] memory tokenIds) = liteRefNFT.loadAllStaticReferences(tokenId); + (address[] memory addresses, uint256[] memory tokenIds) = IPatchworkLiteRef(nft).loadAllStaticReferences(tokenId); for (uint i = 0; i < addresses.length; i++) { if (addresses[i] != address(0)) { _applyAssignedTransfer(addresses[i], from, to, tokenIds[i], nft_, tokenId); @@ -759,20 +755,19 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { /** @dev See {IPatchworkProtocol-updateOwnershipTree} */ - function updateOwnershipTree(address nft, uint256 tokenId) public { - if (IERC165(nft).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { - IPatchworkLiteRef liteRefNFT = IPatchworkLiteRef(nft); - (address[] memory addresses, uint256[] memory tokenIds) = liteRefNFT.loadAllStaticReferences(tokenId); + function updateOwnershipTree(address addr, uint256 tokenId) public { + if (IERC165(addr).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { + (address[] memory addresses, uint256[] memory tokenIds) = IPatchworkLiteRef(addr).loadAllStaticReferences(tokenId); for (uint i = 0; i < addresses.length; i++) { if (addresses[i] != address(0)) { updateOwnershipTree(addresses[i], tokenIds[i]); } } } - if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)) { - IPatchworkSingleAssignableNFT(nft).updateOwnership(tokenId); - } else if (IERC165(nft).supportsInterface(type(IPatchworkPatch).interfaceId)) { - IPatchworkPatch(nft).updateOwnership(tokenId); + if (IERC165(addr).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { + IPatchworkSingleAssignable(addr).updateOwnership(tokenId); + } else if (IERC165(addr).supportsInterface(type(IPatchworkPatch).interfaceId)) { + IPatchworkPatch(addr).updateOwnership(tokenId); } } @@ -843,12 +838,12 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @return frozen if the nft or an owner up the tree is frozen */ function _isFrozen(address nft, uint256 tokenId) private view returns (bool frozen) { - if (IERC165(nft).supportsInterface(type(IPatchworkNFT).interfaceId)) { - if (IPatchworkNFT(nft).frozen(tokenId)) { + if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { + if (IPatchwork721(nft).frozen(tokenId)) { return true; } - if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)) { - (address assignedAddr, uint256 assignedTokenId) = IPatchworkSingleAssignableNFT(nft).getAssignedTo(tokenId); + if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { + (address assignedAddr, uint256 assignedTokenId) = IPatchworkSingleAssignable(nft).getAssignedTo(tokenId); if (assignedAddr != address(0)) { return _isFrozen(assignedAddr, assignedTokenId); } @@ -864,8 +859,8 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @return locked if the nft is locked */ function _isLocked(address nft, uint256 tokenId) private view returns (bool locked) { - if (IERC165(nft).supportsInterface(type(IPatchworkNFT).interfaceId)) { - if (IPatchworkNFT(nft).locked(tokenId)) { + if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { + if (IPatchwork721(nft).locked(tokenId)) { return true; } } diff --git a/test/Patchwork1155Patch.t.sol b/test/Patchwork1155Patch.t.sol index 2ec6d6a..ca81326 100644 --- a/test/Patchwork1155Patch.t.sol +++ b/test/Patchwork1155Patch.t.sol @@ -50,7 +50,7 @@ contract Patchwork1155PatchTest is Test { assertTrue(testAccountPatchNFT.supportsInterface(type(IERC721).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC4906).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC5192).interfaceId)); - assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchwork721).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchwork1155Patch).interfaceId)); } @@ -81,21 +81,21 @@ contract Patchwork1155PatchTest is Test { uint256 b = base1155.mint(_userAddress, 1, 5); // Account patch - _prot.create1155Patch(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); + _prot.patch1155(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); // Account patch can't have duplicate vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ERC1155AlreadyPatched.selector, address(base1155), b, _userAddress, address(test1155PatchNFT))); - _prot.create1155Patch(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); + _prot.patch1155(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); // Global patch - _prot.create1155Patch(_scopeOwner, address(base1155), b, address(0), address(test1155PatchNFT)); + _prot.patch1155(_scopeOwner, address(base1155), b, address(0), address(test1155PatchNFT)); // Global patch can't have duplicate vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ERC1155AlreadyPatched.selector, address(base1155), b, address(0), address(test1155PatchNFT))); - _prot.create1155Patch(_scopeOwner, address(base1155), b, address(0), address(test1155PatchNFT)); + _prot.patch1155(_scopeOwner, address(base1155), b, address(0), address(test1155PatchNFT)); // no user patching allowed vm.stopPrank(); vm.startPrank(_userAddress); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); - _prot.create1155Patch(_scopeOwner, address(base1155), b, address(1), address(test1155PatchNFT)); + _prot.patch1155(_scopeOwner, address(base1155), b, address(1), address(test1155PatchNFT)); } function test1155PatchUserPatch() public { @@ -106,7 +106,7 @@ contract Patchwork1155PatchTest is Test { TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); // user can mint - _prot.create1155Patch(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); + _prot.patch1155(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); } function testBurn() public { @@ -123,11 +123,11 @@ contract Patchwork1155PatchTest is Test { Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot), true); TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); - uint256 pId = _prot.create1155Patch(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); - assertEq(pId, test1155PatchNFT.getTokenIdForOriginalNFT(address(base1155), b, _userAddress)); + uint256 pId = _prot.patch1155(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); + assertEq(pId, test1155PatchNFT.getTokenIdForOriginal1155(address(base1155), b, _userAddress)); // testing not enabled Test1155PatchNFT test1155PatchNFT2 = new Test1155PatchNFT(address(_prot), false); // 0 is default / not-enabled - assertEq(0, test1155PatchNFT2.getTokenIdForOriginalNFT(address(base1155), b, _userAddress)); + assertEq(0, test1155PatchNFT2.getTokenIdForOriginal1155(address(base1155), b, _userAddress)); } } \ No newline at end of file diff --git a/test/PatchworkAccountPatch.t.sol b/test/PatchworkAccountPatch.t.sol index f04e503..b3554c4 100644 --- a/test/PatchworkAccountPatch.t.sol +++ b/test/PatchworkAccountPatch.t.sol @@ -49,7 +49,7 @@ contract PatchworkAccountPatchTest is Test { assertTrue(testAccountPatchNFT.supportsInterface(type(IERC721).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC4906).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC5192).interfaceId)); - assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchwork721).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchworkAccountPatch).interfaceId)); } @@ -59,14 +59,14 @@ contract PatchworkAccountPatchTest is Test { TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, false); // User patching is off, not authorized vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); - _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); + _prot.patchAccount(_userAddress, _user2Address, address(testAccountPatchNFT)); vm.prank(_scopeOwner); - uint256 tokenId = _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); + uint256 tokenId = _prot.patchAccount(_userAddress, _user2Address, address(testAccountPatchNFT)); assertEq(_userAddress, testAccountPatchNFT.ownerOf(tokenId)); // Duplicate should fail vm.prank(_scopeOwner); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.AccountAlreadyPatched.selector, _user2Address, address(testAccountPatchNFT))); - _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); + _prot.patchAccount(_userAddress, _user2Address, address(testAccountPatchNFT)); // Test transfer vm.prank(_userAddress); testAccountPatchNFT.transferFrom(_userAddress, address(55), tokenId); @@ -81,9 +81,9 @@ contract PatchworkAccountPatchTest is Test { TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), true, false); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.MintNotAllowed.selector, _userAddress)); vm.prank(_scopeOwner); - _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); + _prot.patchAccount(_userAddress, _user2Address, address(testAccountPatchNFT)); vm.prank(_scopeOwner); - uint256 tokenId = _prot.createAccountPatch(_userAddress, _userAddress, address(testAccountPatchNFT)); + uint256 tokenId = _prot.patchAccount(_userAddress, _userAddress, address(testAccountPatchNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TransferNotAllowed.selector, address(testAccountPatchNFT), tokenId)); vm.prank(_userAddress); testAccountPatchNFT.transferFrom(_userAddress, address(55), tokenId); @@ -96,14 +96,14 @@ contract PatchworkAccountPatchTest is Test { vm.prank(_scopeOwner); TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, false); // User patching is on - _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); + _prot.patchAccount(_userAddress, _user2Address, address(testAccountPatchNFT)); } function testReverseLookups() public { vm.startPrank(_scopeOwner); TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, true); // User patching is on - uint256 pId = _prot.createAccountPatch(_userAddress, _user2Address, address(testAccountPatchNFT)); + uint256 pId = _prot.patchAccount(_userAddress, _user2Address, address(testAccountPatchNFT)); assertEq(pId, testAccountPatchNFT.getTokenIdForOriginalAccount(_user2Address)); // disabled case TestAccountPatchNFT testAccountPatchNFT2 = new TestAccountPatchNFT(address(_prot), false, false); diff --git a/test/PatchworkFragmentMulti.t.sol b/test/PatchworkFragmentMulti.t.sol index 74ae211..e7dfe7d 100644 --- a/test/PatchworkFragmentMulti.t.sol +++ b/test/PatchworkFragmentMulti.t.sol @@ -50,9 +50,9 @@ contract PatchworkFragmentMultiTest is Test { assertTrue(_testMultiNFT.supportsInterface(type(IERC721).interfaceId)); assertTrue(_testMultiNFT.supportsInterface(type(IERC4906).interfaceId)); assertTrue(_testMultiNFT.supportsInterface(type(IERC5192).interfaceId)); - assertTrue(_testMultiNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); - assertTrue(_testMultiNFT.supportsInterface(type(IPatchworkAssignableNFT).interfaceId)); - assertTrue(_testMultiNFT.supportsInterface(type(IPatchworkMultiAssignableNFT).interfaceId)); + assertTrue(_testMultiNFT.supportsInterface(type(IPatchwork721).interfaceId)); + assertTrue(_testMultiNFT.supportsInterface(type(IPatchworkAssignable).interfaceId)); + assertTrue(_testMultiNFT.supportsInterface(type(IPatchworkMultiAssignable).interfaceId)); } function testMultiAssign() public { @@ -63,19 +63,19 @@ contract PatchworkFragmentMultiTest is Test { uint256 lr3 = _testFragmentLiteRefNFT.mint(_userAddress, ""); // must be registered vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testMultiNFT))); - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); // happy path _testFragmentLiteRefNFT.registerReferenceAddress(address(_testMultiNFT)); - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr1)); - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr2)); - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr3); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr3); assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr3)); assertEq(_testMultiNFT.ownerOf(m1), _user2Address); // don't allow duplicate vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(_testMultiNFT), m1)); - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); // Don't allow duplicate (direct on NFT contract) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(_testMultiNFT), m1)); _testMultiNFT.assign(m1, address(_testFragmentLiteRefNFT), lr2); @@ -83,33 +83,33 @@ contract PatchworkFragmentMultiTest is Test { vm.stopPrank(); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); - _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + _prot.unassign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); vm.prank(_user2Address); - _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + _prot.unassign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, address(500))); vm.prank(address(500)); - _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + _prot.unassign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); vm.startPrank(_scopeOwner); // test unassign - _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + _prot.unassign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); assertFalse(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr2)); - _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + _prot.unassign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); assertFalse(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr1)); - _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr3); + _prot.unassign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr3); assertFalse(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr3)); // not assigned vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssignedToTarget.selector, address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2)); - _prot.unassignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + _prot.unassign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); // not assigned (direct to contract) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssigned.selector, address(_testMultiNFT), m1)); _testMultiNFT.unassign(m1, address(_testFragmentLiteRefNFT), lr2); // test reassign - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr2)); - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr3); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr3); assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr3)); - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr1)); } @@ -122,7 +122,7 @@ contract PatchworkFragmentMultiTest is Test { uint256 lr2 = _testFragmentLiteRefNFT.mint(_userAddress, ""); // must be registered vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testMultiNFT))); - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); // happy path _testFragmentLiteRefNFT.registerReferenceAddress(address(_testMultiNFT)); // as scope owner, should not revert. Only as user if they don't match, but they should work if they match. @@ -130,17 +130,17 @@ contract PatchworkFragmentMultiTest is Test { vm.stopPrank(); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); vm.prank(_user2Address); - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); // target owner should be able to assign to their target as well as the scope owner vm.prank(_userAddress); - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); vm.prank(_scopeOwner); - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); vm.prank(_scopeOwner); uint256 m2 = _testMultiNFT.mint(_userAddress, ""); // This should also work because both are owned by the same user vm.prank(_userAddress); - _prot.assignNFT(address(_testMultiNFT), m2, address(_testFragmentLiteRefNFT), lr2); + _prot.assign(address(_testMultiNFT), m2, address(_testFragmentLiteRefNFT), lr2); } function testGetAssignments() public { @@ -150,14 +150,14 @@ contract PatchworkFragmentMultiTest is Test { uint256[] memory liteRefIds = new uint256[](20); for (uint256 i = 0; i < liteRefIds.length; i++) { liteRefIds[i] = _testFragmentLiteRefNFT.mint(_userAddress, ""); - _prot.assignNFT(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), liteRefIds[i]); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), liteRefIds[i]); } assertEq(20, _testMultiNFT.getAssignmentCount(m1)); - IPatchworkMultiAssignableNFT.Assignment[] memory page1 = _testMultiNFT.getAssignments(m1, 0, 8); - IPatchworkMultiAssignableNFT.Assignment[] memory page2 = _testMultiNFT.getAssignments(m1, 8, 8); - IPatchworkMultiAssignableNFT.Assignment[] memory page3 = _testMultiNFT.getAssignments(m1, 16, 8); - IPatchworkMultiAssignableNFT.Assignment[] memory page4 = _testMultiNFT.getAssignments(m1, 20, 8); - IPatchworkMultiAssignableNFT.Assignment[] memory page5 = _testMultiNFT.getAssignments(m1, 100, 8); + IPatchworkMultiAssignable.Assignment[] memory page1 = _testMultiNFT.getAssignments(m1, 0, 8); + IPatchworkMultiAssignable.Assignment[] memory page2 = _testMultiNFT.getAssignments(m1, 8, 8); + IPatchworkMultiAssignable.Assignment[] memory page3 = _testMultiNFT.getAssignments(m1, 16, 8); + IPatchworkMultiAssignable.Assignment[] memory page4 = _testMultiNFT.getAssignments(m1, 20, 8); + IPatchworkMultiAssignable.Assignment[] memory page5 = _testMultiNFT.getAssignments(m1, 100, 8); assertEq(8, page1.length); assertEq(8, page2.length); assertEq(4, page3.length); @@ -170,7 +170,7 @@ contract PatchworkFragmentMultiTest is Test { assertEq(page3[0].tokenAddr, address(_testFragmentLiteRefNFT)); assertEq(page3[0].tokenId, liteRefIds[16]); // Check non existant - IPatchworkMultiAssignableNFT.Assignment[] memory np1 = _testMultiNFT.getAssignments(11902381, 100, 8); + IPatchworkMultiAssignable.Assignment[] memory np1 = _testMultiNFT.getAssignments(11902381, 100, 8); assertEq(0, np1.length); } @@ -191,6 +191,6 @@ contract PatchworkFragmentMultiTest is Test { _prot.addWhitelist(_scopeName, address(_testFragmentLiteRefNFT)); _testFragmentLiteRefNFT.registerReferenceAddress(address(multi)); uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); - _prot.assignNFT(address(multi), m1, address(_testFragmentLiteRefNFT), lr1); + _prot.assign(address(multi), m1, address(_testFragmentLiteRefNFT), lr1); } } \ No newline at end of file diff --git a/test/PatchworkFragmentSingle.t.sol b/test/PatchworkFragmentSingle.t.sol index ef79893..3289acb 100644 --- a/test/PatchworkFragmentSingle.t.sol +++ b/test/PatchworkFragmentSingle.t.sol @@ -48,9 +48,9 @@ contract PatchworkFragmentSingleTest is Test { assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IERC721).interfaceId)); assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IERC4906).interfaceId)); assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IERC5192).interfaceId)); - assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); - assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IPatchworkAssignableNFT).interfaceId)); - assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IPatchworkSingleAssignableNFT).interfaceId)); + assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IPatchwork721).interfaceId)); + assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IPatchworkAssignable).interfaceId)); + assertTrue(_testFragmentLiteRefNFT.supportsInterface(type(IPatchworkSingleAssignable).interfaceId)); } function testOnAssignedTransferError() public { diff --git a/test/PatchworkNFT.t.sol b/test/PatchworkNFT.t.sol index 6ce61a9..5c4a915 100644 --- a/test/PatchworkNFT.t.sol +++ b/test/PatchworkNFT.t.sol @@ -45,7 +45,7 @@ contract PatchworkNFTTest is Test { assertTrue(_testPatchworkNFT.supportsInterface(type(IERC721).interfaceId)); assertTrue(_testPatchworkNFT.supportsInterface(type(IERC4906).interfaceId)); assertTrue(_testPatchworkNFT.supportsInterface(type(IERC5192).interfaceId)); - assertTrue(_testPatchworkNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); + assertTrue(_testPatchworkNFT.supportsInterface(type(IPatchwork721).interfaceId)); } function testLoadStorePackedMetadataSlot() public { diff --git a/test/PatchworkNFTReferences.t.sol b/test/PatchworkNFTReferences.t.sol index 1ecf226..65fb6ab 100644 --- a/test/PatchworkNFTReferences.t.sol +++ b/test/PatchworkNFTReferences.t.sol @@ -68,21 +68,21 @@ contract PatchworkNFTCombinedTest is Test { assertEq(_userAddress, _testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); // TODO why doesn't this cover the branch != address(0) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); vm.prank(_user2Address); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); // must have user patch enabled - patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); vm.prank(_scopeOwner); - patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); // can't call directly _testFragmentLiteRefNFT.assign(fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); // must be owner/manager - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); vm.prank(_scopeOwner); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); assertEq(_userAddress, _testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); // TODO why doesn't this cover the branch != address(0) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(_testFragmentLiteRefNFT), fragmentTokenId)); vm.prank(_scopeOwner); // not normal to call directly but need to test the correct error @@ -98,14 +98,14 @@ contract PatchworkNFTCombinedTest is Test { _testPatchLiteRefNFT.redactReferenceAddress(refIdx); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentRedacted.selector, address(_testFragmentLiteRefNFT))); vm.prank(_scopeOwner); - _prot.assignNFT(address(_testFragmentLiteRefNFT), newFrag, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), newFrag, address(_testPatchLiteRefNFT), patchTokenId); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); _testPatchLiteRefNFT.unredactReferenceAddress(refIdx); vm.prank(_scopeOwner); _testPatchLiteRefNFT.unredactReferenceAddress(refIdx); vm.prank(_scopeOwner); - _prot.assignNFT(address(_testFragmentLiteRefNFT), newFrag, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), newFrag, address(_testPatchLiteRefNFT), patchTokenId); } function testReferenceAddressErrors() public { diff --git a/test/PatchworkPatch.t.sol b/test/PatchworkPatch.t.sol index d8e0483..091b892 100644 --- a/test/PatchworkPatch.t.sol +++ b/test/PatchworkPatch.t.sol @@ -51,7 +51,7 @@ contract PatchworkPatchTest is Test { assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IERC721).interfaceId)); assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IERC4906).interfaceId)); assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IERC5192).interfaceId)); - assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IPatchworkNFT).interfaceId)); + assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IPatchwork721).interfaceId)); assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IPatchworkLiteRef).interfaceId)); assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IPatchworkPatch).interfaceId)); } @@ -59,7 +59,7 @@ contract PatchworkPatchTest is Test { function testLocks() public { uint256 baseTokenId = _testBaseNFT.mint(_userAddress); vm.prank(_scopeOwner); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); bool locked = _testPatchLiteRefNFT.locked(patchTokenId); assertFalse(locked); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.CannotLockSoulboundPatch.selector, _testPatchLiteRefNFT)); @@ -69,7 +69,7 @@ contract PatchworkPatchTest is Test { function testBurn() public { uint256 baseTokenId = _testBaseNFT.mint(_userAddress); vm.prank(_scopeOwner); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedOperation.selector)); _testPatchLiteRefNFT.burn(patchTokenId); } @@ -78,7 +78,7 @@ contract PatchworkPatchTest is Test { uint256 baseTokenId = _testBaseNFT.mint(_userAddress); vm.prank(_scopeOwner); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); - _prot.createPatch(_user2Address, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + _prot.patch(_user2Address, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); } function testPatchFragment() public { @@ -88,18 +88,18 @@ contract PatchworkPatchTest is Test { uint256 baseTokenId3 = _testBaseNFT.mint(_userAddress); TestPatchFragmentNFT testPatchFragmentNFT = new TestPatchFragmentNFT(address(_prot)); _testPatchLiteRefNFT.registerReferenceAddress(address(testPatchFragmentNFT)); - uint256 liteRefId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); - uint256 liteRefId2 = _prot.createPatch(_user2Address, address(_testBaseNFT), baseTokenId2, address(_testPatchLiteRefNFT)); - uint256 fragmentTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), baseTokenId3, address(testPatchFragmentNFT)); + uint256 liteRefId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); + uint256 liteRefId2 = _prot.patch(_user2Address, address(_testBaseNFT), baseTokenId2, address(_testPatchLiteRefNFT)); + uint256 fragmentTokenId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId3, address(testPatchFragmentNFT)); // check reverse lookups - testPatchLiteRefNFT is disabled, always 0. patchFragmentNFT is enabled - assertEq(0, _testPatchLiteRefNFT.getTokenIdForOriginalNFT(address(_testBaseNFT), baseTokenId)); - assertEq(0, _testPatchLiteRefNFT.getTokenIdForOriginalNFT(address(_testBaseNFT), baseTokenId2)); - assertEq(fragmentTokenId, _testPatchLiteRefNFT.getTokenIdForOriginalNFT(address(testPatchFragmentNFT), baseTokenId3)); + assertEq(0, _testPatchLiteRefNFT.getTokenIdForOriginal721(address(_testBaseNFT), baseTokenId)); + assertEq(0, _testPatchLiteRefNFT.getTokenIdForOriginal721(address(_testBaseNFT), baseTokenId2)); + assertEq(fragmentTokenId, _testPatchLiteRefNFT.getTokenIdForOriginal721(address(testPatchFragmentNFT), baseTokenId3)); // cannot assign patch to a literef that this person does not own vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); - _prot.assignNFT(address(testPatchFragmentNFT), fragmentTokenId, address(_testPatchLiteRefNFT), liteRefId2); + _prot.assign(address(testPatchFragmentNFT), fragmentTokenId, address(_testPatchLiteRefNFT), liteRefId2); // can assign to same owner - _prot.assignNFT(address(testPatchFragmentNFT), fragmentTokenId, address(_testPatchLiteRefNFT), liteRefId); + _prot.assign(address(testPatchFragmentNFT), fragmentTokenId, address(_testPatchLiteRefNFT), liteRefId); // transfer the underlying patched nft and check ownership vm.stopPrank(); assertEq(_userAddress, _testBaseNFT.ownerOf(baseTokenId)); diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index b99cb5a..7f2c798 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -127,7 +127,7 @@ contract PatchworkProtocolTest is Test { _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); - uint256 tokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 tokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); assertEq(tokenId, 0); } @@ -136,7 +136,7 @@ contract PatchworkProtocolTest is Test { _prot.claimScope(_scopeName); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(_testPatchLiteRefNFT))); - _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); } function testCreatePatchNFTVerified() public { @@ -149,7 +149,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.addWhitelist(_scopeName, address(_testPatchLiteRefNFT)); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); - uint256 tokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 tokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); assertEq(tokenId, 0); vm.stopPrank(); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); @@ -158,7 +158,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.removeWhitelist(_scopeName, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(_testPatchLiteRefNFT))); - tokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId + 1, address(_testPatchLiteRefNFT)); + tokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId + 1, address(_testPatchLiteRefNFT)); } function testUserPermissions() public { @@ -174,19 +174,19 @@ contract PatchworkProtocolTest is Test { vm.stopPrank(); vm.startPrank(_userAddress); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); vm.stopPrank(); vm.prank(_scopeOwner); _prot.setScopeRules(_scopeName, true, false, true); vm.startPrank(_userAddress); - patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); vm.prank(_scopeOwner); _prot.setScopeRules(_scopeName, true, true, true); vm.prank(_userAddress); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); // expect revert vm.prank(_userAddress); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TransferBlockedByAssignment.selector, _testFragmentLiteRefNFT, fragmentTokenId)); @@ -195,39 +195,39 @@ contract PatchworkProtocolTest is Test { function testAssignNFT() public { vm.expectRevert(); // not assignable - _prot.assignNFT(address(1), 1, address(1), 1); + _prot.assign(address(1), 1, address(1), 1); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, _scopeName)); - _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.AlreadyPatched.selector, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT))); - patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress, ""); assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _userAddress); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testFragmentLiteRefNFT))); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); //Register artifactNFT to _testPatchLiteRefNFT _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.SelfAssignmentNotAllowed.selector, address(_testFragmentLiteRefNFT), fragmentTokenId)); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testFragmentLiteRefNFT), fragmentTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testFragmentLiteRefNFT), fragmentTokenId); vm.stopPrank(); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); // cover called from non-owner/op with no allowUserAssign - _prot.assignNFTDirect(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId, 0); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId, 0); vm.startPrank(_scopeOwner); - _prot.assignNFTDirect(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId, 0); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId, 0); (address addr, uint256 tokenId) = _testFragmentLiteRefNFT.getAssignedTo(fragmentTokenId); assertEq(addr, address(_testPatchLiteRefNFT)); assertEq(tokenId, patchTokenId); @@ -235,7 +235,7 @@ contract PatchworkProtocolTest is Test { _testFragmentLiteRefNFT.setTestLockOverride(true); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(_testFragmentLiteRefNFT), fragmentTokenId)); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); _testFragmentLiteRefNFT.setTestLockOverride(false); vm.stopPrank(); @@ -255,33 +255,33 @@ contract PatchworkProtocolTest is Test { _testFragmentLiteRefNFT.registerReferenceAddress(address(_testMultiFragmentNFT)); // Single vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, _scopeName)); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId1, address(_testFragmentLiteRefNFT), fragmentTokenId2); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId1, address(_testFragmentLiteRefNFT), fragmentTokenId2); // Multi vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, _scopeName)); - _prot.assignNFT(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2); + _prot.assign(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2); address[] memory fragmentAddresses = new address[](1); uint256[] memory fragments = new uint256[](1); fragmentAddresses[0] = address(_testFragmentLiteRefNFT); fragments[0] = fragmentTokenId1; vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, _scopeName)); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testFragmentLiteRefNFT), fragmentTokenId2); + _prot.assignBatch(fragmentAddresses, fragments, address(_testFragmentLiteRefNFT), fragmentTokenId2); // Claim scope to get assignments done to test unassign _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId1, address(_testFragmentLiteRefNFT), fragmentTokenId2); - _prot.assignNFT(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId1, address(_testFragmentLiteRefNFT), fragmentTokenId2); + _prot.assign(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2); _testFragmentLiteRefNFT.setScopeName("foo"); _testMultiFragmentNFT.setScopeName("foo"); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, "foo")); - _prot.unassignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId1, address(_testFragmentLiteRefNFT), fragmentTokenId2); + _prot.unassign(address(_testFragmentLiteRefNFT), fragmentTokenId1, address(_testFragmentLiteRefNFT), fragmentTokenId2); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, "foo")); - _prot.unassignNFTDirect(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2, 0); + _prot.unassign(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2, 0); } function testUnsupportedNFTUnassign() public { uint256 t1 = _testBaseNFT.mint(_userAddress); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); - _prot.unassignNFT(address(_testBaseNFT), t1, address(_testBaseNFT), t1); + _prot.unassign(address(_testBaseNFT), t1, address(_testBaseNFT), t1); } function testScopeTransferCannotBeFrontrun() public { @@ -305,7 +305,7 @@ contract PatchworkProtocolTest is Test { _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); vm.stopPrank(); vm.startPrank(_userAddress); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress, ""); uint256 user2FragmentTokenId = _testFragmentLiteRefNFT.mint(_user2Address, ""); @@ -313,7 +313,7 @@ contract PatchworkProtocolTest is Test { // Not whitelisted vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(_testFragmentLiteRefNFT))); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); vm.prank(_scopeOwner); _prot.addWhitelist(_scopeName, address(_testFragmentLiteRefNFT)); @@ -321,19 +321,19 @@ contract PatchworkProtocolTest is Test { // user 2 does not own either of these vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); vm.prank(_user2Address); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); // fragment and target have different owners vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); vm.prank(_user2Address); - _prot.assignNFT(address(_testFragmentLiteRefNFT), user2FragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), user2FragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); // fragment and target have different owners vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.startPrank(_userAddress); - _prot.assignNFT(address(_testFragmentLiteRefNFT), user2FragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), user2FragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); (address addr, uint256 tokenId) = _testFragmentLiteRefNFT.getAssignedTo(fragmentTokenId); assertEq(addr, address(_testPatchLiteRefNFT)); assertEq(tokenId, patchTokenId); @@ -342,12 +342,12 @@ contract PatchworkProtocolTest is Test { // not owned by user 2 vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); vm.prank(_user2Address); - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragmentTokenId); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragmentTokenId); vm.startPrank(_userAddress); - _prot.unassignSingleNFTDirect(address(_testFragmentLiteRefNFT), fragmentTokenId, 0); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragmentTokenId, 0); // not currently assigned vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssigned.selector, address(_testFragmentLiteRefNFT), fragmentTokenId)); - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragmentTokenId); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragmentTokenId); } function testDontAssignSomeoneElsesNFT() public { @@ -355,19 +355,19 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_user2Address, ""); assertEq(_testFragmentLiteRefNFT.ownerOf(fragmentTokenId), _user2Address); //Register artifactNFT to _testPatchLiteRefNFT _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); } function testUnassignNFT() public { vm.expectRevert(); // not unassignable - _prot.unassignSingleNFT(address(1), 1); + _prot.unassignSingle(address(1), 1); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); uint256 fragment1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); @@ -376,33 +376,33 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); //Register _testFragmentLiteRefNFT to _testFragmentLiteRefNFT to allow recursion _testFragmentLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); // Assign Id1 -> Id - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); // Assign Id2 -> Id1 - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); + _prot.assign(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); // Now Id2 -> Id1 -> Id, unassign Id2 from Id1 vm.stopPrank(); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragment2); vm.startPrank(_scopeOwner); - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragment2); // Now Id1 -> Id, unassign Id1 from Id - _prot.unassignNFTDirect(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId, 0); + _prot.unassign(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId, 0); // Assign Id1 -> Id - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); // Assign Id2 -> Id1 - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); + _prot.assign(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); // Now Id2 -> Id1 -> Id, unassign Id1 from Id - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment1); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragment1); // Assign Id1 -> Id - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); vm.startPrank(_testBaseNFT.ownerOf(_testBaseNFTTokenId)); // transfer ownership of underlying asset (_testBaseNFT) @@ -412,20 +412,20 @@ contract PatchworkProtocolTest is Test { _testFragmentLiteRefNFT.setGetLiteRefOverride(true, 0); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testFragmentLiteRefNFT))); - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragment2); _testFragmentLiteRefNFT.setGetLiteRefOverride(false, 0); // Revert b/c this isn't the expected assignment given explicitly vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssignedToTarget.selector, address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), 15000)); - _prot.unassignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), 15000); + _prot.unassign(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), 15000); // Now Id2 -> Id1 -> Id where Id belongs to 7, unassign Id2 from Id1 and check new ownership - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragment2); assertEq(_testFragmentLiteRefNFT.ownerOf(fragment2), address(7)); _testFragmentLiteRefNFT.setGetAssignedToOverride(true, address(_testPatchLiteRefNFT)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.RefNotFound.selector, address(_testPatchLiteRefNFT), address(_testFragmentLiteRefNFT), fragment2)); - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragment2); _testFragmentLiteRefNFT.setGetAssignedToOverride(false, address(_testPatchLiteRefNFT)); vm.stopPrank(); @@ -439,23 +439,23 @@ contract PatchworkProtocolTest is Test { function testUnassignMultiNFT() public { vm.expectRevert(); // not unassignable - _prot.unassignMultiNFT(address(1), 1, address(1), 1); + _prot.unassignMulti(address(1), 1, address(1), 1); vm.expectRevert(); // not unassignable - _prot.unassignMultiNFTDirect(address(1), 1, address(1), 1, 0); + _prot.unassignMulti(address(1), 1, address(1), 1, 0); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); uint256 fragment1 = _testMultiFragmentNFT.mint(_userAddress, ""); vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); _testPatchLiteRefNFT.registerReferenceAddress(address(_testMultiFragmentNFT)); - _prot.assignNFT(address(_testMultiFragmentNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); - _prot.unassignNFT(address(_testMultiFragmentNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testMultiFragmentNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + _prot.unassign(address(_testMultiFragmentNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssignedToTarget.selector, address(_testMultiFragmentNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId)); - _prot.unassignNFT(address(_testMultiFragmentNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + _prot.unassign(address(_testMultiFragmentNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); } function testBatchAssignNFT() public { @@ -464,7 +464,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); address[] memory fragmentAddresses = new address[](8); uint256[] memory fragments = new uint256[](8); @@ -475,39 +475,39 @@ contract PatchworkProtocolTest is Test { // Fragment must be registered vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testFragmentLiteRefNFT))); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); uint8 refId = _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); vm.stopPrank(); // User may not assign without userAssign enabled vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); vm.prank(_scopeOwner); _prot.setScopeRules(_scopeName, false, false, true); vm.startPrank(_scopeOwner); // Whitelist enabled requires whitelisted (both patch and fragment) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(_testPatchLiteRefNFT))); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); _prot.addWhitelist(_scopeName, address(_testPatchLiteRefNFT)); // Whitelist enabled requires whitelisted (now just fragment) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(_testFragmentLiteRefNFT))); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); _prot.addWhitelist(_scopeName, address(_testFragmentLiteRefNFT)); // Inputs do not match length vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.BadInputLengths.selector)); - _prot.batchAssignNFT(new address[](1), fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(new address[](1), fragments, address(_testPatchLiteRefNFT), patchTokenId); vm.stopPrank(); vm.prank(_userAddress); _testPatchLiteRefNFT.setFrozen(patchTokenId, true); // It's frozen (patch) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(_testPatchLiteRefNFT), patchTokenId)); vm.prank(_scopeOwner); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); vm.prank(_userAddress); _testPatchLiteRefNFT.setFrozen(patchTokenId, false); @@ -516,7 +516,7 @@ contract PatchworkProtocolTest is Test { // It's frozen (fragment) vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(_testFragmentLiteRefNFT), fragments[0])); vm.prank(_scopeOwner); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); vm.prank(_userAddress); _testFragmentLiteRefNFT.setFrozen(fragments[0], false); @@ -525,7 +525,7 @@ contract PatchworkProtocolTest is Test { // Fragment was redacted vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentRedacted.selector, address(_testFragmentLiteRefNFT))); vm.prank(_scopeOwner); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); vm.prank(_scopeOwner); _testPatchLiteRefNFT.unredactReferenceAddress(refId); @@ -536,14 +536,14 @@ contract PatchworkProtocolTest is Test { // Self-assignment vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.SelfAssignmentNotAllowed.selector, selfAddr[0], selfFrag[0])); vm.prank(_scopeOwner); - _prot.batchAssignNFT(selfAddr, selfFrag, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(selfAddr, selfFrag, address(_testPatchLiteRefNFT), patchTokenId); vm.prank(_userAddress); _testFragmentLiteRefNFT.setLocked(fragments[0], true); // Fragment is locked vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Locked.selector, address(_testFragmentLiteRefNFT), fragments[0])); vm.prank(_scopeOwner); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); vm.prank(_userAddress); _testFragmentLiteRefNFT.setLocked(fragments[0], false); @@ -555,11 +555,11 @@ contract PatchworkProtocolTest is Test { // Target and fragment not same owner vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); vm.prank(_scopeOwner); - _prot.batchAssignNFT(otherUserAddr, otherUserFrag, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(otherUserAddr, otherUserFrag, address(_testPatchLiteRefNFT), patchTokenId); // finally a positive test case vm.prank(_scopeOwner); - _prot.batchAssignNFTDirect(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId, 0); + _prot.assignBatch(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId, 0); for (uint8 i = 0; i < 8; i++) { (address addr, uint256 tokenId) = _testFragmentLiteRefNFT.getAssignedTo(fragments[i]); @@ -571,7 +571,7 @@ contract PatchworkProtocolTest is Test { vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, fragmentAddresses[0], fragments[0])); vm.prank(_scopeOwner); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); } function testBatchUserPatchAssignNFT() public { @@ -584,7 +584,7 @@ contract PatchworkProtocolTest is Test { vm.stopPrank(); vm.startPrank(_userAddress); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); address[] memory fragmentAddresses = new address[](8); uint256[] memory fragments = new uint256[](8); uint256[] memory user2Fragments = new uint256[](8); @@ -598,15 +598,15 @@ contract PatchworkProtocolTest is Test { vm.stopPrank(); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); vm.prank(_user2Address); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); vm.prank(_user2Address); - _prot.batchAssignNFT(fragmentAddresses, user2Fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, user2Fragments, address(_testPatchLiteRefNFT), patchTokenId); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); - _prot.batchAssignNFT(fragmentAddresses, user2Fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, user2Fragments, address(_testPatchLiteRefNFT), patchTokenId); // test assigning fragments for another user address[] memory otherUserAddr = new address[](1); @@ -615,11 +615,11 @@ contract PatchworkProtocolTest is Test { otherUserFrag[0] = _testFragmentLiteRefNFT.mint(_user2Address, ""); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); - _prot.batchAssignNFT(otherUserAddr, otherUserFrag, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(otherUserAddr, otherUserFrag, address(_testPatchLiteRefNFT), patchTokenId); // finally a positive test case vm.startPrank(_userAddress); - _prot.batchAssignNFT(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assignBatch(fragmentAddresses, fragments, address(_testPatchLiteRefNFT), patchTokenId); for (uint8 i = 0; i < 8; i++) { (address addr, uint256 tokenId) = _testFragmentLiteRefNFT.getAssignedTo(fragments[i]); @@ -643,9 +643,9 @@ contract PatchworkProtocolTest is Test { _testFragmentLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); // Assign Id2 -> Id1 - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); + _prot.assign(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); // Assign Id3 -> Id2 - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment3, address(_testFragmentLiteRefNFT), fragment2); + _prot.assign(address(_testFragmentLiteRefNFT), fragment3, address(_testFragmentLiteRefNFT), fragment2); vm.stopPrank(); vm.expectEmit(true, true, true, true, address(_testFragmentLiteRefNFT)); emit Transfer(_userAddress, _user2Address, fragment2); @@ -673,9 +673,9 @@ contract PatchworkProtocolTest is Test { _testFragmentLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); // Assign Id2 -> Id1 - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); + _prot.assign(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); // Assign Id3 -> Id2 - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment3, address(_testFragmentLiteRefNFT), fragment2); + _prot.assign(address(_testFragmentLiteRefNFT), fragment3, address(_testFragmentLiteRefNFT), fragment2); vm.stopPrank(); vm.prank(_userAddress); // This won't actually transfer fragments 2 and 3 @@ -691,7 +691,7 @@ contract PatchworkProtocolTest is Test { // test with patch uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); vm.prank(_scopeOwner); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); vm.prank(_userAddress); _testBaseNFT.transferFrom(_userAddress, _user2Address, _testBaseNFTTokenId); assertEq(_user2Address, _testPatchLiteRefNFT.ownerOf(patchTokenId)); @@ -708,7 +708,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); //Register _testFragmentLiteRefNFT to _testFragmentLiteRefNFT to allow recursion @@ -742,7 +742,7 @@ contract PatchworkProtocolTest is Test { assertEq(false, _testFragmentLiteRefNFT.locked(fragment1)); vm.prank(_scopeOwner); // Assign Id1 -> Id - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); assertEq(true, _testFragmentLiteRefNFT.locked(fragment1)); // cannot lock an assigned fragment @@ -754,7 +754,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_userAddress); _testFragmentLiteRefNFT.setLocked(fragment2, true); vm.expectRevert(); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragment2, address(_testPatchLiteRefNFT), patchTokenId); // cannot transfer a locked fragment vm.expectRevert(); @@ -773,7 +773,7 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentLiteRefNFT)); //Register _testFragmentLiteRefNFT to _testFragmentLiteRefNFT to allow recursion @@ -787,15 +787,15 @@ contract PatchworkProtocolTest is Test { vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(_testPatchLiteRefNFT), patchTokenId)); vm.prank(_scopeOwner); // Assign Id1 -> Id - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); vm.prank(_userAddress); _testPatchLiteRefNFT.setFrozen(patchTokenId, false); vm.startPrank(_scopeOwner); assertEq(1, _testPatchLiteRefNFT.getFreezeNonce(patchTokenId)); // Assign Id1 -> Id - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); + _prot.assign(address(_testFragmentLiteRefNFT), fragment1, address(_testPatchLiteRefNFT), patchTokenId); // Assign Id2 -> Id1 - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); + _prot.assign(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); // Lock the patch, shouldn't allow unassignment of any child vm.stopPrank(); @@ -806,17 +806,17 @@ contract PatchworkProtocolTest is Test { vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(_testFragmentLiteRefNFT), fragment2)); vm.prank(_scopeOwner); // Now Id2 -> Id1 -> Id, unassign Id2 from Id1 - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragment2); vm.prank(_userAddress); _testPatchLiteRefNFT.setFrozen(patchTokenId, false); assertEq(2, _testPatchLiteRefNFT.getFreezeNonce(patchTokenId)); vm.startPrank(_scopeOwner); // Now Id2 -> Id1 -> Id, unassign Id2 from Id1 - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment2); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragment2); vm.stopPrank(); vm.startPrank(_scopeOwner); // Unassign Id1 from patch - _prot.unassignSingleNFT(address(_testFragmentLiteRefNFT), fragment1); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragment1); vm.stopPrank(); // Lock the fragment, shouldn't allow assignment to anything @@ -824,12 +824,12 @@ contract PatchworkProtocolTest is Test { _testFragmentLiteRefNFT.setFrozen(fragment1, true); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(_testFragmentLiteRefNFT), fragment1)); vm.prank(_scopeOwner); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); + _prot.assign(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); vm.prank(_userAddress); _testFragmentLiteRefNFT.setFrozen(fragment2, true); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.Frozen.selector, address(_testFragmentLiteRefNFT), fragment1)); vm.prank(_scopeOwner); - _prot.assignNFT(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); + _prot.assign(address(_testFragmentLiteRefNFT), fragment2, address(_testFragmentLiteRefNFT), fragment1); vm.startPrank(_userAddress); _testFragmentLiteRefNFT.setFrozen(fragment2, false); @@ -890,10 +890,10 @@ contract PatchworkProtocolTest is Test { uint256 frag1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); uint256 frag2 = testFrag2.mint(_userAddress, ""); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); - uint256 patchTokenId = _prot.createPatch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); - _prot.assignNFT(address(_testFragmentLiteRefNFT), frag1, address(_testPatchLiteRefNFT), patchTokenId); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + _prot.assign(address(_testFragmentLiteRefNFT), frag1, address(_testPatchLiteRefNFT), patchTokenId); // The second assign succeeding combined with the assertion that they are equal ref values means there is no collision in the scope. - _prot.assignNFT(address(testFrag2), frag2, address(_testFragmentLiteRefNFT), frag1); + _prot.assign(address(testFrag2), frag2, address(_testFragmentLiteRefNFT), frag1); // LiteRef IDs should match because it is idx1 tokenID 0 for both (0x1. 0x0) (uint64 lr1,) = _testPatchLiteRefNFT.getLiteReference(address(_testFragmentLiteRefNFT), frag1); (uint64 lr2,) = _testFragmentLiteRefNFT.getLiteReference(address(testFrag2), frag2); diff --git a/test/TestDynamicArrayLiteRefNFT.t.sol b/test/TestDynamicArrayLiteRefNFT.t.sol index 300101c..6a6ce22 100644 --- a/test/TestDynamicArrayLiteRefNFT.t.sol +++ b/test/TestDynamicArrayLiteRefNFT.t.sol @@ -80,7 +80,7 @@ contract PatchworkAccountPatchTest is Test { (uint64 ref,) = nft.getLiteReference(address(0x55), i); refs[i] = ref; } - nft.batchAddReferences(m, refs); + nft.addReferenceBatch(m, refs); assertEq(11, nft.getDynamicReferenceCount(m)); (, uint256[] memory tokenIds) = nft.loadDynamicReferencePage(m, 0, 3); diff --git a/test/nfts/Test1155PatchNFT.sol b/test/nfts/Test1155PatchNFT.sol index 5bf2b4d..09427ab 100644 --- a/test/nfts/Test1155PatchNFT.sol +++ b/test/nfts/Test1155PatchNFT.sol @@ -12,7 +12,7 @@ contract Test1155PatchNFT is Patchwork1155Patch { uint256 thing; } - constructor(address manager_, bool reverseEnabled_) PatchworkNFT("testscope", "Test1155PatchNFT", "TPLR", msg.sender, manager_) { + constructor(address manager_, bool reverseEnabled_) Patchwork721("testscope", "Test1155PatchNFT", "TPLR", msg.sender, manager_) { _reverseEnabled = reverseEnabled_; } diff --git a/test/nfts/TestAccountPatchNFT.sol b/test/nfts/TestAccountPatchNFT.sol index 1f76eac..c164f22 100644 --- a/test/nfts/TestAccountPatchNFT.sol +++ b/test/nfts/TestAccountPatchNFT.sol @@ -13,7 +13,7 @@ contract TestAccountPatchNFT is PatchworkAccountPatch { uint256 thing; } - constructor(address manager_, bool sameOwnerModel_, bool reverseEnabled_) PatchworkNFT("testscope", "TestAccountPatchNFT", "TPLR", msg.sender, manager_) { + constructor(address manager_, bool sameOwnerModel_, bool reverseEnabled_) Patchwork721("testscope", "TestAccountPatchNFT", "TPLR", msg.sender, manager_) { _sameOwnerModel = sameOwnerModel_; _reverseEnabled = reverseEnabled_; } diff --git a/test/nfts/TestDynamicArrayLiteRefNFT.sol b/test/nfts/TestDynamicArrayLiteRefNFT.sol index ca1cda5..85325f4 100644 --- a/test/nfts/TestDynamicArrayLiteRefNFT.sol +++ b/test/nfts/TestDynamicArrayLiteRefNFT.sol @@ -9,7 +9,7 @@ pragma solidity ^0.8.13; Has metadata as defined in totem-metadata.json */ -import "../../src/PatchworkNFT.sol"; +import "../../src/Patchwork721.sol"; import "../../src/PatchworkLiteRef.sol"; import "../../src/IPatchworkMintable.sol"; import "forge-std/console.sol"; @@ -29,18 +29,18 @@ struct DynamicLiteRefs { mapping(uint64 => uint256) idx; } -contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef, IPatchworkMintable { +contract TestDynamicArrayLiteRefNFT is Patchwork721, PatchworkLiteRef, IPatchworkMintable { uint256 _nextTokenId; mapping(uint256 => DynamicLiteRefs) internal _dynamicLiterefStorage; // tokenId => indexed slots - constructor(address manager_) PatchworkNFT("testscope", "TestPatchLiteRef", "TPLR", msg.sender, manager_) PatchworkLiteRef() { + constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", msg.sender, manager_) PatchworkLiteRef() { } // ERC-165 - function supportsInterface(bytes4 interfaceID) public view virtual override(PatchworkNFT, PatchworkLiteRef) returns (bool) { - return PatchworkNFT.supportsInterface(interfaceID) || + function supportsInterface(bytes4 interfaceID) public view virtual override(Patchwork721, PatchworkLiteRef) returns (bool) { + return Patchwork721.supportsInterface(interfaceID) || PatchworkLiteRef.supportsInterface(interfaceID); } @@ -55,8 +55,8 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef, IPatchwor _manager = manager_; } - function getScopeName() public view override (PatchworkNFT, IPatchworkScoped) returns (string memory scopeName) { - return PatchworkNFT.getScopeName(); + function getScopeName() public view override (Patchwork721, IPatchworkScoped) returns (string memory scopeName) { + return Patchwork721.getScopeName(); } function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { @@ -187,7 +187,7 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef, IPatchwor } } - function batchAddReferences(uint256 ourTokenId, uint64[] calldata _liteRefs) public override { + function addReferenceBatch(uint256 ourTokenId, uint64[] calldata _liteRefs) public override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); // do in batches of 4 with 1 remainder pass DynamicLiteRefs storage store = _dynamicLiterefStorage[ourTokenId]; @@ -287,7 +287,7 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef, IPatchwor } } - function addReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { + function addReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { if (targetMetadataId != 0) { revert("Unsupported metadata ID"); } @@ -295,18 +295,18 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef, IPatchwor } - function removeReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { + function removeReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { if (targetMetadataId != 0) { revert("Unsupported metadata ID"); } removeReference(tokenId, liteRef); } - function batchAddReferencesDirect(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) public override { + function addReferenceBatch(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) public override { if (targetMetadataId != 0) { revert("Unsupported metadata ID"); } - batchAddReferences(tokenId, liteRefs); + addReferenceBatch(tokenId, liteRefs); } function loadReferenceAddressAndTokenId(uint256 ourTokenId, uint256 idx) public view returns (address addr, uint256 tokenId) { @@ -367,8 +367,8 @@ contract TestDynamicArrayLiteRefNFT is PatchworkNFT, PatchworkLiteRef, IPatchwor } } - function _checkWriteAuth() internal override(PatchworkNFT, PatchworkLiteRef) view returns (bool allow) { - return PatchworkNFT._checkWriteAuth(); + function _checkWriteAuth() internal override(Patchwork721, PatchworkLiteRef) view returns (bool allow) { + return Patchwork721._checkWriteAuth(); } function burn(uint256 tokenId) public { diff --git a/test/nfts/TestFragmentLiteRefNFT.sol b/test/nfts/TestFragmentLiteRefNFT.sol index f12feae..180e050 100644 --- a/test/nfts/TestFragmentLiteRefNFT.sol +++ b/test/nfts/TestFragmentLiteRefNFT.sol @@ -36,7 +36,7 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef, IP bool _getAssignedToOverrideSet; address _getAssignedToOverride; - constructor (address _manager) PatchworkNFT("testscope", "TestFragmentLiteRef", "TFLR", msg.sender, _manager) { + constructor (address _manager) Patchwork721("testscope", "TestFragmentLiteRef", "TFLR", msg.sender, _manager) { } // ERC-165 @@ -47,7 +47,7 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef, IP } function getScopeName() public view override (PatchworkFragmentSingle, IPatchworkScoped) returns (string memory scopeName) { - return PatchworkNFT.getScopeName(); + return Patchwork721.getScopeName(); } function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { @@ -152,29 +152,28 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef, IP } } - function addReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { + function addReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { if (targetMetadataId != 0) { revert("Unsupported metadata ID"); } addReference(tokenId, liteRef); } - - function removeReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { + function removeReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { if (targetMetadataId != 0) { revert("Unsupported metadata ID"); } removeReference(tokenId, liteRef); } - function batchAddReferencesDirect(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) public view override { + function addReferenceBatch(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) public view override { if (targetMetadataId != 0) { revert("Unsupported metadata ID"); } - batchAddReferences(tokenId, liteRefs); + addReferenceBatch(tokenId, liteRefs); } - function batchAddReferences(uint256 ourTokenId, uint64[] calldata /*_liteRefs*/) public view override { + function addReferenceBatch(uint256 ourTokenId, uint64[] calldata /*_liteRefs*/) public view override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); // TODO bulk insert for fewer stores } @@ -228,8 +227,8 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef, IP return (addresses, tokenIds); } - function _checkWriteAuth() internal override(PatchworkNFT, PatchworkLiteRef) view returns (bool allow) { - return PatchworkNFT._checkWriteAuth(); + function _checkWriteAuth() internal override(Patchwork721, PatchworkLiteRef) view returns (bool allow) { + return Patchwork721._checkWriteAuth(); } // Function for mocking test behaviors - set to true for it to return unlocked always diff --git a/test/nfts/TestMultiFragmentNFT.sol b/test/nfts/TestMultiFragmentNFT.sol index 1cefd2b..43b7afd 100644 --- a/test/nfts/TestMultiFragmentNFT.sol +++ b/test/nfts/TestMultiFragmentNFT.sol @@ -12,7 +12,7 @@ struct TestMultiFragmentNFTMetadata { contract TestMultiFragmentNFT is PatchworkFragmentMulti, IPatchworkMintable { uint256 _nextTokenId; - constructor (address _manager) PatchworkNFT("testscope", "TestMultiFragmentNFT", "TFLR", msg.sender, _manager) { + constructor (address _manager) Patchwork721("testscope", "TestMultiFragmentNFT", "TFLR", msg.sender, _manager) { } function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { @@ -21,7 +21,7 @@ contract TestMultiFragmentNFT is PatchworkFragmentMulti, IPatchworkMintable { } function getScopeName() public view override (PatchworkFragmentMulti, IPatchworkScoped) returns (string memory scopeName) { - return PatchworkNFT.getScopeName(); + return Patchwork721.getScopeName(); } function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { diff --git a/test/nfts/TestPatchFragmentNFT.sol b/test/nfts/TestPatchFragmentNFT.sol index 68096c6..adc765e 100644 --- a/test/nfts/TestPatchFragmentNFT.sol +++ b/test/nfts/TestPatchFragmentNFT.sol @@ -26,7 +26,7 @@ contract TestPatchFragmentNFT is PatchworkPatch, PatchworkFragmentSingle { uint256 _nextTokenId; - constructor(address manager_) PatchworkNFT("testscope", "TestPatchFragment", "TPLR", msg.sender, manager_) PatchworkFragmentSingle() { + constructor(address manager_) Patchwork721("testscope", "TestPatchFragment", "TPLR", msg.sender, manager_) PatchworkFragmentSingle() { } // ERC-165 diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index ff32213..e837f43 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -27,7 +27,7 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { uint256 _nextTokenId; - constructor(address manager_) PatchworkNFT("testscope", "TestPatchLiteRef", "TPLR", msg.sender, manager_) PatchworkLiteRef() { + constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", msg.sender, manager_) PatchworkLiteRef() { } // ERC-165 @@ -165,7 +165,7 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { } } - function batchAddReferences(uint256 ourTokenId, uint64[] calldata /*_liteRefs*/) public view override { + function addReferenceBatch(uint256 ourTokenId, uint64[] calldata /*_liteRefs*/) public view override { require(_checkTokenWriteAuth(ourTokenId), "not authorized"); // TODO bulk insert for fewer stores } @@ -213,7 +213,7 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { } } - function addReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { + function addReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { if (targetMetadataId != 0) { revert("Unsupported metadata ID"); } @@ -221,18 +221,18 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { } - function removeReferenceDirect(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { + function removeReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { if (targetMetadataId != 0) { revert("Unsupported metadata ID"); } removeReference(tokenId, liteRef); } - function batchAddReferencesDirect(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) public view override { + function addReferenceBatch(uint256 tokenId, uint64[] calldata liteRefs, uint256 targetMetadataId) public view override { if (targetMetadataId != 0) { revert("Unsupported metadata ID"); } - batchAddReferences(tokenId, liteRefs); + addReferenceBatch(tokenId, liteRefs); } function loadReferenceAddressAndTokenId(uint256 ourTokenId, uint256 idx) public view returns (address addr, uint256 tokenId) { @@ -258,8 +258,8 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { return (addresses, tokenIds); } - function _checkWriteAuth() internal override(PatchworkNFT, PatchworkLiteRef) view returns (bool allow) { - return PatchworkNFT._checkWriteAuth(); + function _checkWriteAuth() internal override(Patchwork721, PatchworkLiteRef) view returns (bool allow) { + return Patchwork721._checkWriteAuth(); } function burn(uint256 tokenId) public { diff --git a/test/nfts/TestPatchworkNFT.sol b/test/nfts/TestPatchworkNFT.sol index 0ceb02c..07ecc01 100644 --- a/test/nfts/TestPatchworkNFT.sol +++ b/test/nfts/TestPatchworkNFT.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "../../src/PatchworkNFT.sol"; +import "../../src/Patchwork721.sol"; import "../../src/IPatchworkMintable.sol"; -contract TestPatchworkNFT is PatchworkNFT, IPatchworkMintable { +contract TestPatchworkNFT is Patchwork721, IPatchworkMintable { uint256 _nextTokenId; @@ -12,11 +12,11 @@ contract TestPatchworkNFT is PatchworkNFT, IPatchworkMintable { uint256 thing; } - constructor(address manager_) PatchworkNFT("testscope", "TestPatchworkNFT", "TPLR", msg.sender, manager_) { + constructor(address manager_) Patchwork721("testscope", "TestPatchworkNFT", "TPLR", msg.sender, manager_) { } function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { - return PatchworkNFT.supportsInterface(interfaceID) || + return Patchwork721.supportsInterface(interfaceID) || interfaceID == type(IPatchworkMintable).interfaceId; } @@ -34,8 +34,8 @@ contract TestPatchworkNFT is PatchworkNFT, IPatchworkMintable { return MetadataSchema(1, entries); } - function getScopeName() public view override (PatchworkNFT, IPatchworkScoped) returns (string memory scopeName) { - return PatchworkNFT.getScopeName(); + function getScopeName() public view override (Patchwork721, IPatchworkScoped) returns (string memory scopeName) { + return Patchwork721.getScopeName(); } function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { From eab1d002b8fc908bb498b12d450a756fa9d400bb Mon Sep 17 00:00:00 2001 From: Rob Green Date: Wed, 29 Nov 2023 18:25:21 -0800 Subject: [PATCH 25/63] V2 test coverage (#48) * coverage * coverage * coverage * Protocol with full coverage again * coverage * Coverage --- src/Patchwork721.sol | 1 + src/PatchworkFragmentSingle.sol | 2 +- src/PatchworkProtocol.sol | 9 ++ test/Fees.t.sol | 156 +++++++++++++++++++++++++-- test/PatchworkFragmentMulti.t.sol | 2 + test/PatchworkNFT.t.sol | 12 +++ test/PatchworkNFTReferences.t.sol | 14 ++- test/PatchworkProtocol.t.sol | 18 ++-- test/PatchworkUtils.t.sol | 7 +- test/nfts/TestFragmentLiteRefNFT.sol | 2 +- test/nfts/TestFragmentSingleNFT.sol | 111 +++++++++++++++++++ test/nfts/TestMultiFragmentNFT.sol | 2 +- test/nfts/TestPatchLiteRefNFT.sol | 6 +- test/nfts/TestPatchNFT.sol | 107 ++++++++++++++++++ 14 files changed, 422 insertions(+), 27 deletions(-) create mode 100644 test/nfts/TestFragmentSingleNFT.sol create mode 100644 test/nfts/TestPatchNFT.sol diff --git a/src/Patchwork721.sol b/src/Patchwork721.sol index 0f92696..bb16840 100644 --- a/src/Patchwork721.sol +++ b/src/Patchwork721.sol @@ -117,6 +117,7 @@ abstract contract Patchwork721 is ERC721, IPatchwork721, IERC4906 { return interfaceID == type(IPatchwork721).interfaceId || interfaceID == type(IERC5192).interfaceId || interfaceID == type(IERC4906).interfaceId || + interfaceID == type(IPatchworkScoped).interfaceId || ERC721.supportsInterface(interfaceID); } diff --git a/src/PatchworkFragmentSingle.sol b/src/PatchworkFragmentSingle.sol index f6d908e..ef0a296 100644 --- a/src/PatchworkFragmentSingle.sol +++ b/src/PatchworkFragmentSingle.sol @@ -20,7 +20,7 @@ abstract contract PatchworkFragmentSingle is Patchwork721, IPatchworkSingleAssig mapping(uint256 => Assignment) internal _assignments; /** - @dev See {IPatchwork721-getScopeName} + @dev See {IPatchworkScoped-getScopeName} */ function getScopeName() public view virtual override (Patchwork721, IPatchworkScoped) returns (string memory) { return _scopeName; diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 89a6c54..a2c5a97 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -352,6 +352,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-patch} */ function patch(address owner, address originalAddress, uint originalTokenId, address patchAddress) external payable returns (uint256 tokenId) { + if (!IERC165(patchAddress).supportsInterface(type(IPatchworkPatch).interfaceId)) { + revert UnsupportedContract(); + } IPatchworkPatch patch_ = IPatchworkPatch(patchAddress); string memory scopeName = patch_.getScopeName(); Scope storage scope = _mustHaveScope(scopeName); @@ -379,6 +382,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-patch1155} */ function patch1155(address to, address originalAddress, uint originalTokenId, address originalAccount, address patchAddress) external payable returns (uint256 tokenId) { + if (!IERC165(patchAddress).supportsInterface(type(IPatchwork1155Patch).interfaceId)) { + revert UnsupportedContract(); + } IPatchwork1155Patch patch_ = IPatchwork1155Patch(patchAddress); string memory scopeName = patch_.getScopeName(); Scope storage scope = _mustHaveScope(scopeName); @@ -405,6 +411,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-patchAccount} */ function patchAccount(address owner, address originalAddress, address patchAddress) external payable returns (uint256 tokenId) { + if (!IERC165(patchAddress).supportsInterface(type(IPatchworkAccountPatch).interfaceId)) { + revert UnsupportedContract(); + } IPatchworkAccountPatch patch_ = IPatchworkAccountPatch(patchAddress); string memory scopeName = patch_.getScopeName(); Scope storage scope = _mustHaveScope(scopeName); diff --git a/test/Fees.t.sol b/test/Fees.t.sol index 7af12c6..5a5dd90 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -12,6 +12,7 @@ import "./nfts/TestDynamicArrayLiteRefNFT.sol"; import "./nfts/TestMultiFragmentNFT.sol"; import "./nfts/TestPatchLiteRefNFT.sol"; import "./nfts/TestAccountPatchNFT.sol"; +import "./nfts/TestBaseNFT.sol"; contract FeesTest is Test { @@ -41,6 +42,7 @@ contract FeesTest is Test { _prot.claimScope(_scopeName); _prot.setScopeRules(_scopeName, false, false, false); vm.stopPrank(); + vm.deal(_scopeOwner, 2 ether); } function testProtocolBankers() public { @@ -48,6 +50,21 @@ contract FeesTest is Test { _prot.addProtocolBanker(_defaultUser); vm.prank(_patchworkOwner); _prot.addProtocolBanker(_user2Address); + + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _prot.setProtocolFeeConfig(IPatchworkProtocol.ProtocolFeeConfig(1000, 1000, 1000)); + vm.prank(_patchworkOwner); + _prot.setProtocolFeeConfig(IPatchworkProtocol.ProtocolFeeConfig(150, 150, 150)); + IPatchworkProtocol.ProtocolFeeConfig memory feeConfig = _prot.getProtocolFeeConfig(); + assertEq(150, feeConfig.mintBp); + assertEq(150, feeConfig.assignBp); + assertEq(150, feeConfig.patchBp); + vm.prank(_user2Address); + _prot.setProtocolFeeConfig(IPatchworkProtocol.ProtocolFeeConfig(1000, 1000, 1000)); + + vm.prank(_patchworkOwner); + _prot.addProtocolBanker(_defaultUser); + vm.startPrank(_scopeOwner); TestFragmentLiteRefNFT lr = new TestFragmentLiteRefNFT(address(_prot)); _prot.addWhitelist(_scopeName, address(lr)); @@ -61,7 +78,8 @@ contract FeesTest is Test { assertEq(900000000, _prot.balanceOf(_scopeName)); assertEq(100000000, _prot.balanceOfProtocol()); // default user not authorized - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); _prot.withdrawFromProtocol(100000000); vm.prank(_user2Address); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InsufficientFunds.selector)); @@ -69,6 +87,9 @@ contract FeesTest is Test { vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); _prot.withdrawFromProtocol(50000000); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FailedToSend.selector)); + vm.prank(_defaultUser); + _prot.withdrawFromProtocol(50000000); // banker + owner should work vm.prank(_user2Address); _prot.withdrawFromProtocol(50000000); @@ -93,6 +114,7 @@ contract FeesTest is Test { _prot.addWhitelist(_scopeName, address(lr)); _prot.setMintConfiguration(address(lr), IPatchworkProtocol.MintConfig(1000000000, true)); _prot.addBanker(_scopeName, _user2Address); + _prot.addBanker(_scopeName, _defaultUser); vm.stopPrank(); // mint something just to get some money in the account IPatchworkProtocol.MintConfig memory mc = _prot.getMintConfiguration(address(lr)); @@ -104,6 +126,9 @@ contract FeesTest is Test { vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); vm.prank(_userAddress); _prot.withdraw(_scopeName, 450000000); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FailedToSend.selector)); + vm.prank(_defaultUser); + _prot.withdraw(_scopeName, 450000000); vm.prank(_user2Address); _prot.withdraw(_scopeName, 450000000); vm.prank(_scopeOwner); @@ -119,14 +144,36 @@ contract FeesTest is Test { _prot.withdraw(_scopeName, 1); } + function testUnsupportedContracts() public { + vm.startPrank(_scopeOwner); + TestBaseNFT tBase = new TestBaseNFT(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + _prot.setMintConfiguration(address(tBase), IPatchworkProtocol.MintConfig(1000000000, true)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + _prot.getMintConfiguration(address(tBase)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + _prot.setPatchFee(address(tBase), 1); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + _prot.getPatchFee(address(tBase)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + _prot.setAssignFee(address(tBase), 1); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + _prot.getAssignFee(address(tBase)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + _prot.mint(_userAddress, address(tBase), ""); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + _prot.mintBatch(_userAddress, address(tBase), "", 5); + } + function testMints() public { vm.startPrank(_scopeOwner); - _prot.setScopeRules(_scopeName, false, false, true); TestFragmentLiteRefNFT lr = new TestFragmentLiteRefNFT(address(_prot)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.MintNotActive.selector)); + _prot.mint(_userAddress, address(lr), ""); + _prot.setScopeRules(_scopeName, false, false, true); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(lr))); _prot.setMintConfiguration(address(lr), IPatchworkProtocol.MintConfig(1000000000, true)); vm.stopPrank(); - // mint something just to get some money in the account IPatchworkProtocol.MintConfig memory mc = _prot.getMintConfiguration(address(lr)); uint256 mintCost = mc.flatFee; assertEq(0, mintCost); // Couldn't be set due to whitelisting @@ -139,10 +186,11 @@ contract FeesTest is Test { _prot.addWhitelist(_scopeName, address(lr)); _prot.setMintConfiguration(address(lr), IPatchworkProtocol.MintConfig(1000000000, true)); vm.stopPrank(); - // mint something just to get some money in the account mc = _prot.getMintConfiguration(address(lr)); mintCost = mc.flatFee; assertEq(1000000000, mintCost); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.mint{value: 50}(_userAddress, address(lr), ""); _prot.mint{value: mintCost}(_userAddress, address(lr), ""); assertEq(900000000, _prot.balanceOf(_scopeName)); assertEq(100000000, _prot.balanceOfProtocol()); @@ -155,7 +203,6 @@ contract FeesTest is Test { vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(lr))); _prot.setMintConfiguration(address(lr), IPatchworkProtocol.MintConfig(1000000000, true)); vm.stopPrank(); - // mint something just to get some money in the account IPatchworkProtocol.MintConfig memory mc = _prot.getMintConfiguration(address(lr)); uint256 mintCost = mc.flatFee; assertEq(0, mintCost); // Couldn't be set due to whitelisting @@ -168,16 +215,107 @@ contract FeesTest is Test { _prot.addWhitelist(_scopeName, address(lr)); _prot.setMintConfiguration(address(lr), IPatchworkProtocol.MintConfig(1000000000, true)); vm.stopPrank(); - // mint something just to get some money in the account mc = _prot.getMintConfiguration(address(lr)); mintCost = mc.flatFee * 5; assertEq(5000000000, mintCost); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.mintBatch{value: 50}(_userAddress, address(lr), "", 5); _prot.mintBatch{value: mintCost}(_userAddress, address(lr), "", 5); assertEq(4500000000, _prot.balanceOf(_scopeName)); assertEq(500000000, _prot.balanceOfProtocol()); } - // TODO patch fees - // TODO assign fees - + function testPatchFees() public { + vm.startPrank(_scopeOwner); + _prot.setScopeRules(_scopeName, false, false, true); + TestBaseNFT tBase = new TestBaseNFT(); + TestBase1155 tBase1155 = new TestBase1155(); + TestPatchLiteRefNFT t721 = new TestPatchLiteRefNFT(address(_prot)); + Test1155PatchNFT t1155 = new Test1155PatchNFT(address(_prot), false); + TestAccountPatchNFT tAccount = new TestAccountPatchNFT(address(_prot), false, false); + vm.stopPrank(); + + // 721 + _testPatchFees(address(t721)); + vm.startPrank(_scopeOwner); + uint256 tId = tBase.mint(_userAddress); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.patch(_userAddress, address(tBase), tId, address(t721)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.patch{value: 1 ether}(_userAddress, address(tBase), tId, address(t721)); + _prot.patch{value: _prot.getPatchFee(address(t721))}(_userAddress, address(tBase), tId, address(t721)); + vm.stopPrank(); + + // 1155 + _testPatchFees(address(t1155)); + vm.startPrank(_scopeOwner); + tId = tBase1155.mint(_userAddress, 1, 1); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.patch1155(_userAddress, address(tBase1155), tId, _userAddress, address(t1155)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.patch1155{value: 1 ether}(_userAddress, address(tBase1155), tId, _userAddress, address(t1155)); + _prot.patch1155{value: _prot.getPatchFee(address(t1155))}(_userAddress, address(tBase1155), tId, _userAddress, address(t1155)); + vm.stopPrank(); + + // account + _testPatchFees(address(tAccount)); + vm.startPrank(_scopeOwner); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.patchAccount(_userAddress, _user2Address, address(tAccount)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.patchAccount{value: 1 ether}(_userAddress, _user2Address, address(tAccount)); + _prot.patchAccount{value: _prot.getPatchFee(address(tAccount))}(_userAddress, _user2Address, address(tAccount)); + vm.stopPrank(); + + // patch wrong types + vm.startPrank(_scopeOwner); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + _prot.patch(_userAddress, address(tBase), 2, address(t1155)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + _prot.patch1155(_userAddress, address(tBase1155), 3, address(0), address(t721)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + _prot.patchAccount(_userAddress, _user2Address, address(t721)); + } + + function _testPatchFees(address patchAddr) private { + vm.startPrank(_scopeOwner); + // error cases + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, patchAddr)); + _prot.setPatchFee(patchAddr, 1); + _prot.addWhitelist(_scopeName, patchAddr); + vm.stopPrank(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); + _prot.setPatchFee(patchAddr, 1); + // success + vm.startPrank(_scopeOwner); + _prot.setPatchFee(patchAddr, 1); + vm.stopPrank(); + assertEq(1, _prot.getPatchFee(patchAddr)); + } + + function testAssignFees() public { + vm.startPrank(_scopeOwner); + _prot.setScopeRules(_scopeName, false, false, true); + TestFragmentLiteRefNFT nft = new TestFragmentLiteRefNFT(address(_prot)); + nft.registerReferenceAddress(address(nft)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(nft))); + _prot.setAssignFee(address(nft), 1); + _prot.addWhitelist(_scopeName, address(nft)); + vm.stopPrank(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); + _prot.setAssignFee(address(nft), 1); + // success + vm.startPrank(_scopeOwner); + _prot.setAssignFee(address(nft), 1); + assertEq(1, _prot.getAssignFee(address(nft))); + uint256 n1 = nft.mint(_userAddress, ""); + uint256 n2 = nft.mint(_userAddress, ""); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.assign(address(nft), n2, address(nft), n1); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.assign{value: 1 ether}(address(nft), n2, address(nft), n1); + _prot.assign{value: _prot.getAssignFee(address(nft))}(address(nft), n2, address(nft), n1); + } } \ No newline at end of file diff --git a/test/PatchworkFragmentMulti.t.sol b/test/PatchworkFragmentMulti.t.sol index e7dfe7d..642d4e9 100644 --- a/test/PatchworkFragmentMulti.t.sol +++ b/test/PatchworkFragmentMulti.t.sol @@ -147,6 +147,8 @@ contract PatchworkFragmentMultiTest is Test { vm.startPrank(_scopeOwner); uint256 m1 = _testMultiNFT.mint(_user2Address, ""); _testFragmentLiteRefNFT.registerReferenceAddress(address(_testMultiNFT)); + IPatchworkMultiAssignable.Assignment[] memory page0 = _testMultiNFT.getAssignments(m1, 0, 8); + assertEq(0, page0.length); uint256[] memory liteRefIds = new uint256[](20); for (uint256 i = 0; i < liteRefIds.length; i++) { liteRefIds[i] = _testFragmentLiteRefNFT.mint(_userAddress, ""); diff --git a/test/PatchworkNFT.t.sol b/test/PatchworkNFT.t.sol index 5c4a915..5d31825 100644 --- a/test/PatchworkNFT.t.sol +++ b/test/PatchworkNFT.t.sol @@ -57,6 +57,18 @@ contract PatchworkNFTTest is Test { assertEq(0x505050, _testPatchworkNFT.loadPackedMetadataSlot(n, 0)); } + function testLoadStorePackedMetadata() public { + uint256 n = _testPatchworkNFT.mint(_userAddress, ""); + uint256[] memory slots = _testPatchworkNFT.loadPackedMetadata(n); + slots[0] = 0x505050; + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _testPatchworkNFT.storePackedMetadata(n, slots); + vm.prank(_scopeOwner); + _testPatchworkNFT.storePackedMetadata(n, slots); + slots = _testPatchworkNFT.loadPackedMetadata(n); + assertEq(0x505050, slots[0]); + } + function testTransferFrom() public { // TODO make sure these are calling checkTransfer on proto uint256 n = _testPatchworkNFT.mint(_userAddress, ""); diff --git a/test/PatchworkNFTReferences.t.sol b/test/PatchworkNFTReferences.t.sol index 65fb6ab..75fb52a 100644 --- a/test/PatchworkNFTReferences.t.sol +++ b/test/PatchworkNFTReferences.t.sol @@ -8,12 +8,14 @@ import "../src/PatchworkProtocol.sol"; import "./nfts/TestPatchLiteRefNFT.sol"; import "./nfts/TestFragmentLiteRefNFT.sol"; import "./nfts/TestBaseNFT.sol"; +import "./nfts/TestFragmentSingleNFT.sol"; contract PatchworkNFTCombinedTest is Test { PatchworkProtocol _prot; TestBaseNFT _testBaseNFT; TestPatchLiteRefNFT _testPatchLiteRefNFT; TestFragmentLiteRefNFT _testFragmentLiteRefNFT; + TestFragmentSingleNFT _testFragmentSingleNFT; string _scopeName; address _defaultUser; @@ -38,6 +40,7 @@ contract PatchworkNFTCombinedTest is Test { _testPatchLiteRefNFT = new TestPatchLiteRefNFT(address(_prot)); _testFragmentLiteRefNFT = new TestFragmentLiteRefNFT(address(_prot)); + _testFragmentSingleNFT = new TestFragmentSingleNFT(address(_prot)); vm.stopPrank(); vm.prank(_userAddress); @@ -62,10 +65,14 @@ contract PatchworkNFTCombinedTest is Test { assertEq(address(_testFragmentLiteRefNFT), refAddr); assertEq(1, tokenId); + vm.prank(_scopeOwner); + _testPatchLiteRefNFT.registerReferenceAddress(address(_testFragmentSingleNFT)); // test assign perms uint256 baseTokenId = _testBaseNFT.mint(_userAddress); uint256 fragmentTokenId = _testFragmentLiteRefNFT.mint(_userAddress, ""); - assertEq(_userAddress, _testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); // TODO why doesn't this cover the branch != address(0) + uint256 fragSingleTokenId = _testFragmentSingleNFT.mint(_userAddress, ""); + assertEq(_userAddress, _testFragmentSingleNFT.ownerOf(fragSingleTokenId)); + assertEq(_userAddress, _testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _user2Address)); vm.prank(_user2Address); uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); @@ -81,9 +88,12 @@ contract PatchworkNFTCombinedTest is Test { vm.prank(_userAddress); // must be owner/manager _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); + vm.prank(_scopeOwner); + _prot.assign(address(_testFragmentSingleNFT), fragSingleTokenId, address(_testPatchLiteRefNFT), patchTokenId); + assertEq(_userAddress, _testFragmentSingleNFT.ownerOf(fragSingleTokenId)); vm.prank(_scopeOwner); _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); - assertEq(_userAddress, _testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); // TODO why doesn't this cover the branch != address(0) + assertEq(_userAddress, _testFragmentLiteRefNFT.ownerOf(fragmentTokenId)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(_testFragmentLiteRefNFT), fragmentTokenId)); vm.prank(_scopeOwner); // not normal to call directly but need to test the correct error _testFragmentLiteRefNFT.assign(fragmentTokenId, address(_testPatchLiteRefNFT), patchTokenId); diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index 7f2c798..f317e97 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -10,6 +10,7 @@ import "./nfts/TestFragmentLiteRefNFT.sol"; import "./nfts/TestBaseNFT.sol"; import "./nfts/TestPatchworkNFT.sol"; import "./nfts/TestMultiFragmentNFT.sol"; +import "./nfts/TestPatchNFT.sol"; contract PatchworkProtocolTest is Test { event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); @@ -344,7 +345,7 @@ contract PatchworkProtocolTest is Test { vm.prank(_user2Address); _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragmentTokenId); vm.startPrank(_userAddress); - _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragmentTokenId, 0); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragmentTokenId, 1); // not currently assigned vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssigned.selector, address(_testFragmentLiteRefNFT), fragmentTokenId)); _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragmentTokenId); @@ -689,15 +690,16 @@ contract PatchworkProtocolTest is Test { assertEq(_user2Address, _testFragmentLiteRefNFT.unassignedOwnerOf(fragment3)); // test with patch + TestPatchNFT patch = new TestPatchNFT(address(_prot)); uint256 _testBaseNFTTokenId = _testBaseNFT.mint(_userAddress); vm.prank(_scopeOwner); - uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(_testPatchLiteRefNFT)); + uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), _testBaseNFTTokenId, address(patch)); vm.prank(_userAddress); _testBaseNFT.transferFrom(_userAddress, _user2Address, _testBaseNFTTokenId); - assertEq(_user2Address, _testPatchLiteRefNFT.ownerOf(patchTokenId)); - assertEq(_userAddress, _testPatchLiteRefNFT.unpatchedOwnerOf(patchTokenId)); - _prot.updateOwnershipTree(address(_testPatchLiteRefNFT), patchTokenId); - assertEq(_user2Address, _testPatchLiteRefNFT.unpatchedOwnerOf(patchTokenId)); + assertEq(_user2Address, patch.ownerOf(patchTokenId)); + assertEq(_userAddress, patch.unpatchedOwnerOf(patchTokenId)); + _prot.updateOwnershipTree(address(patch), patchTokenId); + assertEq(_user2Address, patch.unpatchedOwnerOf(patchTokenId)); } function testLocks() public { @@ -812,9 +814,9 @@ contract PatchworkProtocolTest is Test { assertEq(2, _testPatchLiteRefNFT.getFreezeNonce(patchTokenId)); vm.startPrank(_scopeOwner); // Now Id2 -> Id1 -> Id, unassign Id2 from Id1 - _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragment2); + _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragment2, 0); vm.stopPrank(); - vm.startPrank(_scopeOwner); + vm.startPrank(_scopeOwner); // Unassign Id1 from patch _prot.unassignSingle(address(_testFragmentLiteRefNFT), fragment1); vm.stopPrank(); diff --git a/test/PatchworkUtils.t.sol b/test/PatchworkUtils.t.sol index bc44981..dd1c98b 100644 --- a/test/PatchworkUtils.t.sol +++ b/test/PatchworkUtils.t.sol @@ -7,7 +7,7 @@ import "forge-std/console.sol"; import "../src/PatchworkUtils.sol"; contract PatchworkUtilsTest is Test { - function testStringConversions() public { + function testStringConversions() public { bytes8 b8; // 9/8 bytes memory ns = bytes("abcdefghi"); @@ -96,4 +96,9 @@ contract PatchworkUtilsTest is Test { } assertEq("", PatchworkUtils.toString32(uint256(b32))); } + + function testByteConversions() public { + assertEq(abi.encodePacked(bytes1(uint8(0)), bytes1(uint8(0))), PatchworkUtils.convertUint16ToBytes(0)); + assertEq(abi.encodePacked(bytes1(uint8(1)), bytes1(uint8(2))), PatchworkUtils.convertUint16ToBytes(258)); + } } \ No newline at end of file diff --git a/test/nfts/TestFragmentLiteRefNFT.sol b/test/nfts/TestFragmentLiteRefNFT.sol index 180e050..56a649e 100644 --- a/test/nfts/TestFragmentLiteRefNFT.sol +++ b/test/nfts/TestFragmentLiteRefNFT.sol @@ -47,7 +47,7 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef, IP } function getScopeName() public view override (PatchworkFragmentSingle, IPatchworkScoped) returns (string memory scopeName) { - return Patchwork721.getScopeName(); + return PatchworkFragmentSingle.getScopeName(); } function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { diff --git a/test/nfts/TestFragmentSingleNFT.sol b/test/nfts/TestFragmentSingleNFT.sol new file mode 100644 index 0000000..9fbce1b --- /dev/null +++ b/test/nfts/TestFragmentSingleNFT.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +/* + Prototype - Generated Patchwork Meta contract for Totem NFT. + + Is attached to any normal NFT and is scoped for a specific application. + + Has metadata as defined in totem-metadata.json +*/ + +import "../../src/PatchworkPatch.sol"; +import "../../src/PatchworkFragmentSingle.sol"; + +struct TestFragmentSingleNFTMetadata { + uint16 xp; + uint8 level; + uint16 xpLost; + uint16 stakedMade; + uint16 stakedCorrect; + uint8 evolution; + string nickname; +} + +contract TestFragmentSingleNFT is PatchworkFragmentSingle { + + uint256 _nextTokenId; + + constructor(address manager_) Patchwork721("testscope", "TestPatchFragment", "TPLR", msg.sender, manager_) PatchworkFragmentSingle() { + } + + function schemaURI() pure external override returns (string memory) { + return "https://mything/my-metadata.json"; + } + + function imageURI(uint256 _tokenId) pure external override returns (string memory) {} + + function setManager(address manager_) external { + require(_checkWriteAuth()); + _manager = manager_; + } + + /* + Hard coded prototype schema is: + slot 0 offset 0 = artifactIDs (spans 2) - also we need special built-in handling for < 256 bit IDs + slot 2 offset 0 = xp + slot 2 offset 16 = level + slot 2 offset 24 = xpLost + slot 2 offset 40 = stakedMade + slot 2 offset 56 = stakedCorrect + slot 2 offset 72 = evolution + slot 2 offset 80 = nickname + */ + function schema() pure external override returns (MetadataSchema memory) { + MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](8); + entries[1] = MetadataSchemaEntry(1, 1, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 0, "xp"); + entries[2] = MetadataSchemaEntry(2, 2, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 2, 16, "level"); + entries[3] = MetadataSchemaEntry(3, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 24, "xpLost"); + entries[4] = MetadataSchemaEntry(4, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 40, "stakedMade"); + entries[5] = MetadataSchemaEntry(5, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 2, 56, "stakedCorrect"); + entries[6] = MetadataSchemaEntry(6, 0, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 2, 72, "evolution"); + entries[7] = MetadataSchemaEntry(7, 0, FieldType.CHAR16, 1, FieldVisibility.PUBLIC, 2, 80, "nickname"); + return MetadataSchema(1, entries); + } + + function packMetadata(TestFragmentSingleNFTMetadata memory data) public pure returns (uint256[] memory slots) { + bytes32 nickname; + bytes memory ns = bytes(data.nickname); + + assembly { + nickname := mload(add(ns, 32)) + } + slots = new uint256[](1); + slots[0] = uint256(data.xp) | uint256(data.level) << 16 | uint256(data.xpLost) << 24 | uint256(data.stakedMade) << 40 | uint256(data.stakedCorrect) << 56 | uint256(data.evolution) << 72 | uint256(nickname) >> 128 << 80; + return slots; + } + + function storeMetadata(uint256 _tokenId, TestFragmentSingleNFTMetadata memory data) public { + require(_checkTokenWriteAuth(_tokenId), "not authorized"); + _metadataStorage[_tokenId] = packMetadata(data); + } + + function unpackMetadata(uint256[] memory slots) public pure returns (TestFragmentSingleNFTMetadata memory data) { + data.xp = uint16(slots[0]); + data.level = uint8(slots[0] >> 16); + data.xpLost = uint16(slots[0] >> 24); + data.stakedMade = uint16(slots[0] >> 40); + data.stakedCorrect = uint16(slots[0] >> 56); + data.evolution = uint8(slots[0] >> 72); + data.nickname = string(abi.encodePacked(bytes16(uint128(slots[0] >> 80)))); + return data; + } + + function loadMetadata(uint256 _tokenId) public view returns (TestFragmentSingleNFTMetadata memory data) { + return unpackMetadata(_metadataStorage[_tokenId]); + } + + function mint(address to, bytes memory data) external returns (uint256 tokenId){ + // Just for testing + tokenId = _nextTokenId; + _nextTokenId++; + _safeMint(to, tokenId); + _metadataStorage[tokenId] = new uint256[](1); + return tokenId; + } + + function burn(uint256 tokenId) public { + // test only + _burn(tokenId); + } +} \ No newline at end of file diff --git a/test/nfts/TestMultiFragmentNFT.sol b/test/nfts/TestMultiFragmentNFT.sol index 43b7afd..99cea72 100644 --- a/test/nfts/TestMultiFragmentNFT.sol +++ b/test/nfts/TestMultiFragmentNFT.sol @@ -21,7 +21,7 @@ contract TestMultiFragmentNFT is PatchworkFragmentMulti, IPatchworkMintable { } function getScopeName() public view override (PatchworkFragmentMulti, IPatchworkScoped) returns (string memory scopeName) { - return Patchwork721.getScopeName(); + return PatchworkFragmentMulti.getScopeName(); } function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index e837f43..4cd5c3b 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -221,10 +221,8 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { } - function removeReference(uint256 tokenId, uint64 liteRef, uint256 targetMetadataId) public override { - if (targetMetadataId != 0) { - revert("Unsupported metadata ID"); - } + function removeReference(uint256 tokenId, uint64 liteRef, uint256 /*targetMetadataId*/) public override { + // Work with any given metadataId just for coverage testing removeReference(tokenId, liteRef); } diff --git a/test/nfts/TestPatchNFT.sol b/test/nfts/TestPatchNFT.sol new file mode 100644 index 0000000..b92d6f3 --- /dev/null +++ b/test/nfts/TestPatchNFT.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +/* + Prototype - Generated Patchwork Meta contract for Totem NFT. + + Is attached to any normal NFT and is scoped for a specific application. + + Has metadata as defined in totem-metadata.json +*/ + +import "../../src/PatchworkPatch.sol"; + +struct TestPatchNFTMetadata { + uint16 xp; + uint8 level; + uint16 xpLost; + uint16 stakedMade; + uint16 stakedCorrect; + uint8 evolution; + string nickname; +} + +contract TestPatchNFT is PatchworkPatch { + + uint256 _nextTokenId; + + constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", msg.sender, manager_) { + } + + function schemaURI() pure external override returns (string memory) { + return "https://mything/my-metadata.json"; + } + + function imageURI(uint256 _tokenId) pure external override returns (string memory) {} + + function setManager(address manager_) external { + require(_checkWriteAuth()); + _manager = manager_; + } + + function schema() pure external override returns (MetadataSchema memory) { + MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](7); + entries[0] = MetadataSchemaEntry(0, 1, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 0, 0, "xp"); + entries[1] = MetadataSchemaEntry(1, 2, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 0, 16, "level"); + entries[2] = MetadataSchemaEntry(2, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 0, 24, "xpLost"); + entries[3] = MetadataSchemaEntry(3, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 0, 40, "stakedMade"); + entries[4] = MetadataSchemaEntry(4, 0, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 0, 56, "stakedCorrect"); + entries[5] = MetadataSchemaEntry(5, 0, FieldType.UINT8, 1, FieldVisibility.PUBLIC, 0, 72, "evolution"); + entries[6] = MetadataSchemaEntry(6, 0, FieldType.CHAR16, 1, FieldVisibility.PUBLIC, 0, 80, "nickname"); + return MetadataSchema(1, entries); + } + + function packMetadata(TestPatchNFTMetadata memory data) public pure returns (uint256[] memory slots) { + bytes32 nickname; + bytes memory ns = bytes(data.nickname); + + assembly { + nickname := mload(add(ns, 32)) + } + slots = new uint256[](1); + slots[0] = uint256(data.xp) | uint256(data.level) << 16 | uint256(data.xpLost) << 24 | uint256(data.stakedMade) << 40 | uint256(data.stakedCorrect) << 56 | uint256(data.evolution) << 72 | uint256(nickname) >> 128 << 80; + return slots; + } + + function storeMetadata(uint256 _tokenId, TestPatchNFTMetadata memory data) public { + require(_checkTokenWriteAuth(_tokenId), "not authorized"); + _metadataStorage[_tokenId] = packMetadata(data); + } + + function unpackMetadata(uint256[] memory slots) public pure returns (TestPatchNFTMetadata memory data) { + data.xp = uint16(slots[0]); + data.level = uint8(slots[0] >> 16); + data.xpLost = uint16(slots[0] >> 24); + data.stakedMade = uint16(slots[0] >> 40); + data.stakedCorrect = uint16(slots[0] >> 56); + data.evolution = uint8(slots[0] >> 72); + data.nickname = string(abi.encodePacked(bytes16(uint128(slots[0] >> 80)))); + return data; + } + + function loadMetadata(uint256 _tokenId) public view returns (TestPatchNFTMetadata memory data) { + return unpackMetadata(_metadataStorage[_tokenId]); + } + + function mintPatch(address owner, address originalNFTAddress, uint originalNFTTokenId) external returns (uint256 tokenId){ + if (msg.sender != _manager) { + revert(); + } + // require inherited ownership + if (IERC721(originalNFTAddress).ownerOf(originalNFTTokenId) != owner) { + revert IPatchworkProtocol.NotAuthorized(owner); + } + // Just for testing + tokenId = _nextTokenId; + _nextTokenId++; + _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, false); + _safeMint(owner, tokenId); + _metadataStorage[tokenId] = new uint256[](1); + return tokenId; + } + + function burn(uint256 tokenId) public { + // test only - protocol does not currently support this as you can't mint another patch later + _burn(tokenId); + } +} \ No newline at end of file From 39e1965d968b51b393e414df746e7cb6ab947f0d Mon Sep 17 00:00:00 2001 From: Rob Green Date: Wed, 29 Nov 2023 19:39:40 -0800 Subject: [PATCH 26/63] some ascii fun (#49) --- src/PatchworkProtocol.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index a2c5a97..d3f8f5e 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -1,6 +1,20 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; +/** + + ____ __ __ __ + / __ \____ _/ /______/ /_ _ ______ _____/ /__ + / /_/ / __ `/ __/ ___/ __ \ | /| / / __ \/ ___/ //_/ + / ____/ /_/ / /_/ /__/ / / / |/ |/ / /_/ / / / ,< +/_/ ___\__,_/\__/\___/_/ /_/|__/|__/\____/_/ /_/|_| + / __ \_________ / /_____ _________ / / + / /_/ / ___/ __ \/ __/ __ \/ ___/ __ \/ / + / ____/ / / /_/ / /_/ /_/ / /__/ /_/ / / +/_/ /_/ \____/\__/\____/\___/\____/_/ + +*/ + import "./IPatchwork721.sol"; import "./IPatchworkSingleAssignable.sol"; import "./IPatchworkMultiAssignable.sol"; From a7245fcba8615b275a6a87168c64a8936e68b6f6 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Wed, 29 Nov 2023 21:11:44 -0800 Subject: [PATCH 27/63] natspec completion (#50) --- src/IPatchworkMintable.sol | 17 ++- src/IPatchworkProtocol.sol | 194 ++++++++++++++++++++++------ src/PatchworkProtocol.sol | 66 +++++++++- src/PatchworkUtils.sol | 5 + test/nfts/TestFragmentSingleNFT.sol | 2 +- 5 files changed, 244 insertions(+), 40 deletions(-) diff --git a/src/IPatchworkMintable.sol b/src/IPatchworkMintable.sol index 4a04bee..419acf5 100644 --- a/src/IPatchworkMintable.sol +++ b/src/IPatchworkMintable.sol @@ -8,8 +8,23 @@ import "./IPatchworkScoped.sol"; @author Runic Labs, Inc */ interface IPatchworkMintable is IPatchworkScoped { - // TODO docs + + /** + @notice Mint a new token + @dev Mints a single token to a specified address. + @param to The address to which the token will be minted. + @param data Additional data to be passed to the minting process. + @return tokenId The ID of the minted token. + */ function mint(address to, bytes calldata data) external payable returns (uint256 tokenId); + /** + @notice Mint a batch of new tokens + @dev Mints multiple tokens to a specified address. + @param to The address to which the tokens will be minted. + @param data Additional data to be passed to the minting process. + @param quantity The number of tokens to mint. + @return tokenIds An array of the IDs of the minted tokens. + */ function mintBatch(address to, bytes calldata data, uint256 quantity) external payable returns (uint256[] memory tokenIds); } \ No newline at end of file diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index c7b5d80..dd5215e 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -252,7 +252,7 @@ interface IPatchworkProtocol { @notice Mint configuration */ struct MintConfig { - uint256 flatFee; /// wei + uint256 flatFee; /// fee per 1 quantity mint in wei bool active; /// If the mint is active } @@ -303,53 +303,37 @@ interface IPatchworkProtocol { */ mapping(address => bool) whitelist; + /** + @notice Mapped list of mint configurations for this scope + @dev Address of the IPatchworkMintable mapped to the configuration + */ mapping(address => MintConfig) mintConfigurations; + /** + @notice Mapped list of patch fees for this scope + @dev Address of a 721, 1155 or account patch mapped to the fee in wei + */ mapping(address => uint256) patchFees; + /** + @notice Mapped list of assign fees for this scope + @dev Address of an IPatchworkAssignable mapped to the fee in wei + */ mapping(address => uint256) assignFees; + /** + @notice Balance in wei for this scope + @dev accrued in mint, patch and assign fees, may only be withdrawn by scope bankers + */ uint256 balance; + /** + @notice Mapped list of addresses that are designated bankers for this scope + @dev Address mapped to a boolean indicating if they are a banker + */ mapping(address => bool) bankers; } - function setMintConfiguration(address addr, MintConfig memory config) external; - - function getMintConfiguration(address addr) external view returns (MintConfig memory config); - - function setPatchFee(address addr, uint256 baseFee) external; - - function getPatchFee(address addr) external view returns (uint256 baseFee); - - function setAssignFee(address fragmentAddress, uint256 baseFee) external; - - function getAssignFee(address fragmentAddress) external view returns (uint256 baseFee); - - function addBanker(string memory scopeName, address addr) external; - - function removeBanker(string memory scopeName, address addr) external; - - function withdraw(string memory scopeName, uint256 amount) external; - - function balanceOf(string memory scopeName) external view returns (uint256 balance); - - function mint(address to, address mintable, bytes calldata data) external payable returns (uint256 tokenId); - - function mintBatch(address to, address mintable, bytes calldata data, uint256 quantity) external payable returns (uint256[] memory tokenIds); - - function setProtocolFeeConfig(ProtocolFeeConfig memory config) external; - - function getProtocolFeeConfig() external view returns (ProtocolFeeConfig memory config); - - function addProtocolBanker(address addr) external; - - function removeProtocolBanker(address addr) external; - - function withdrawFromProtocol(uint256 balance) external; - - function balanceOfProtocol() external view returns (uint256 balance); - /** @notice Emitted when a fragment is assigned @param owner The owner of the target and fragment @@ -625,6 +609,142 @@ interface IPatchworkProtocol { */ function removeWhitelist(string calldata scopeName, address addr) external; + /** + @notice Set the mint configuration for a given address + @param addr The address for which to set the mint configuration, must be IPatchworkMintable + @param config The mint configuration to be set + */ + function setMintConfiguration(address addr, MintConfig memory config) external; + + /** + @notice Get the mint configuration for a given address + @param addr The address for which to get the mint configuration + @return config The mint configuration of the given address + */ + function getMintConfiguration(address addr) external view returns (MintConfig memory config); + + /** + @notice Set the patch fee for a given address + @dev must be banker of scope claimed by addr to call + @param addr The address for which to set the patch fee + @param baseFee The patch fee to be set in wei + */ + function setPatchFee(address addr, uint256 baseFee) external; + + /** + @notice Get the patch fee for a given address + @param addr The address for which to get the patch fee + @return baseFee The patch fee of the given address in wei + */ + function getPatchFee(address addr) external view returns (uint256 baseFee); + + /** + @notice Set the assign fee for a given fragment address + @dev must be banker of scope claimed by fragmentAddress to call + @param fragmentAddress The address of the fragment for which to set the fee + @param baseFee The assign fee to be set in wei + */ + function setAssignFee(address fragmentAddress, uint256 baseFee) external; + + /** + @notice Get the assign fee for a given fragment address + @param fragmentAddress The address of the fragment for which to get the fee + @return baseFee The assign fee of the given fragment address in wei + */ + function getAssignFee(address fragmentAddress) external view returns (uint256 baseFee); + + /** + @notice Add a banker to a given scope + @dev must be owner of scope to call + @param scopeName The name of the scope + @param addr The address to be added as a banker + */ + function addBanker(string memory scopeName, address addr) external; + + /** + @notice Remove a banker from a given scope + @dev must be owner of scope to call + @param scopeName The name of the scope + @param addr The address to be removed as a banker + */ + function removeBanker(string memory scopeName, address addr) external; + + /** + @notice Withdraw an amount from the balance of a given scope + @dev must be owner of scope or banker of scope to call + @dev transfers to the msg.sender + @param scopeName The name of the scope + @param amount The amount to be withdrawn in wei + */ + function withdraw(string memory scopeName, uint256 amount) external; + + /** + @notice Get the balance of a given scope + @param scopeName The name of the scope + @return balance The balance of the given scope in wei + */ + function balanceOf(string memory scopeName) external view returns (uint256 balance); + + /** + @notice Mint a new token + @param to The address to which the token will be minted + @param mintable The address of the IPatchworkMintable contract + @param data Additional data to be passed to the minting + @return tokenId The ID of the minted token + */ + function mint(address to, address mintable, bytes calldata data) external payable returns (uint256 tokenId); + + /** + @notice Mint a batch of new tokens + @param to The address to which the tokens will be minted + @param mintable The address of the IPatchworkMintable contract + @param data Additional data to be passed to the minting + @param quantity The number of tokens to mint + @return tokenIds An array of the IDs of the minted tokens + */ + function mintBatch(address to, address mintable, bytes calldata data, uint256 quantity) external payable returns (uint256[] memory tokenIds); + + /** + @notice Set the protocol fee configuration + @dev must be protocol owner or banker to call + @param config The protocol fee configuration to be set + */ + function setProtocolFeeConfig(ProtocolFeeConfig memory config) external; + + /** + @notice Get the current protocol fee configuration + @return config The current protocol fee configuration + */ + function getProtocolFeeConfig() external view returns (ProtocolFeeConfig memory config); + + /** + @notice Add a banker to the protocol + @dev must be protocol owner to call + @param addr The address to be added as a protocol banker + */ + function addProtocolBanker(address addr) external; + + /** + @notice Remove a banker from the protocol + @dev must be protocol owner to call + @param addr The address to be removed as a protocol banker + */ + function removeProtocolBanker(address addr) external; + + /** + @notice Withdraw a specified amount from the protocol balance + @dev must be protocol owner or banker to call + @dev transfers to the msg.sender + @param balance The amount to be withdrawn in wei + */ + function withdrawFromProtocol(uint256 balance) external; + + /** + @notice Get the current balance of the protocol + @return balance The balance of the protocol in wei + */ + function balanceOfProtocol() external view returns (uint256 balance); + /** @notice Create a new patch @param owner The owner of the patch diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index d3f8f5e..cce7d16 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -50,18 +50,26 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { */ mapping(bytes32 => bool) _uniquePatches; + /// Balance of the protocol uint256 _protocolBalance; + /** + @notice protocol bankers + @dev Map of addresses authorized to set fees and withdraw funds for the protocol + @dev Does not allow for scope balance withdrawl + */ mapping(address => bool) _protocolBankers; + /// Current protocol fee configuration ProtocolFeeConfig _protocolFeeConfig; // TODO overrides? mapping(string => ProtocolFeeConfig) _scopeFeeOverrides; // scope-based fee overrides - mapping(address => uint256) _addressBpOverride; + // TODO maybe not necessary uint256 public constant TRANSFER_GAS_LIMIT = 5000; + /// Constructor constructor() Ownable() ReentrancyGuard() {} /** @@ -161,6 +169,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { emit ScopeRuleChange(scopeName, msg.sender, allowUserPatch, allowUserAssign, requireWhitelist); } + /** + @dev See {IPatchworkProtocol-setMintConfiguration} + */ function setMintConfiguration(address addr, MintConfig memory config) public { if (!IERC165(addr).supportsInterface(type(IPatchworkMintable).interfaceId)) { revert UnsupportedContract(); @@ -174,6 +185,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { emit MintConfigure(scopeName, msg.sender, addr, config); } + /** + @dev See {IPatchworkProtocol-getMintConfiguration} + */ function getMintConfiguration(address addr) public view returns (MintConfig memory config) { if (!IERC165(addr).supportsInterface(type(IPatchworkMintable).interfaceId)) { revert UnsupportedContract(); @@ -182,6 +196,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { return scope.mintConfigurations[addr]; } + /** + @dev See {IPatchworkProtocol-setPatchFee} + */ function setPatchFee(address addr, uint256 baseFee) public { if (!IERC165(addr).supportsInterface(type(IPatchworkScoped).interfaceId)) { revert UnsupportedContract(); @@ -193,6 +210,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { scope.patchFees[addr] = baseFee; } + /** + @dev See {IPatchworkProtocol-getPatchFee} + */ function getPatchFee(address addr) public view returns (uint256 baseFee) { if (!IERC165(addr).supportsInterface(type(IPatchworkScoped).interfaceId)) { revert UnsupportedContract(); @@ -201,6 +221,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { return scope.patchFees[addr]; } + /** + @dev See {IPatchworkProtocol-setAssignFee} + */ function setAssignFee(address fragmentAddress, uint256 baseFee) public { if (!IERC165(fragmentAddress).supportsInterface(type(IPatchworkScoped).interfaceId)) { revert UnsupportedContract(); @@ -212,6 +235,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { scope.assignFees[fragmentAddress] = baseFee; } + /** + @dev See {IPatchworkProtocol-getAssignFee} + */ function getAssignFee(address fragmentAddress) public view returns (uint256 baseFee) { if (!IERC165(fragmentAddress).supportsInterface(type(IPatchworkScoped).interfaceId)) { revert UnsupportedContract(); @@ -220,6 +246,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { return scope.assignFees[fragmentAddress]; } + /** + @dev See {IPatchworkProtocol-addBanker} + */ function addBanker(string memory scopeName, address addr) public { Scope storage scope = _mustHaveScope(scopeName); _mustBeOwnerOrOperator(scope); @@ -227,6 +256,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { emit ScopeBankerAdd(scopeName, msg.sender, addr); } + /** + @dev See {IPatchworkProtocol-removeBanker} + */ function removeBanker(string memory scopeName, address addr) public { Scope storage scope = _mustHaveScope(scopeName); _mustBeOwnerOrOperator(scope); @@ -234,6 +266,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { emit ScopeBankerRemove(scopeName, msg.sender, addr); } + /** + @dev See {IPatchworkProtocol-withdraw} + */ function withdraw(string memory scopeName, uint256 amount) public nonReentrant { Scope storage scope = _mustHaveScope(scopeName); if (msg.sender != scope.owner && !scope.bankers[msg.sender]) { @@ -253,11 +288,17 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { emit ScopeWithdraw(scopeName, msg.sender, amount); } + /** + @dev See {IPatchworkProtocol-balanceOf} + */ function balanceOf(string memory scopeName) public view returns (uint256 balance) { Scope storage scope = _mustHaveScope(scopeName); return scope.balance; } + /** + @dev See {IPatchworkProtocol-mint} + */ function mint(address to, address mintable, bytes calldata data) external payable returns (uint256 tokenId) { (MintConfig memory config, string memory scopeName, Scope storage scope) = _setupMint(mintable); if (msg.value != config.flatFee) { @@ -268,6 +309,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { emit Mint(msg.sender, scopeName, to, mintable, data); } + /** + @dev See {IPatchworkProtocol-mintBatch} + */ function mintBatch(address to, address mintable, bytes calldata data, uint256 quantity) external payable returns (uint256[] memory tokenIds) { (MintConfig memory config, string memory scopeName, Scope storage scope) = _setupMint(mintable); uint256 totalFee = config.flatFee * quantity; @@ -279,6 +323,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { emit MintBatch(msg.sender, scopeName, to, mintable, data, quantity); } + /// Common to mints function _setupMint(address mintable) internal view returns (MintConfig memory config, string memory scopeName, Scope storage scope) { if (!IERC165(mintable).supportsInterface(type(IPatchworkMintable).interfaceId)) { revert UnsupportedContract(); @@ -292,6 +337,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } } + /// Common to mints function _handleMintFee(Scope storage scope) internal { // Account for 100% of the message value if (msg.value != 0) { @@ -301,6 +347,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } } + /** + @dev See {IPatchworkProtocol-setProtocolFeeConfig} + */ function setProtocolFeeConfig(ProtocolFeeConfig memory config) public { if (msg.sender != owner() && _protocolBankers[msg.sender] == false) { revert NotAuthorized(msg.sender); @@ -308,20 +357,32 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { _protocolFeeConfig = config; } + /** + @dev See {IPatchworkProtocol-getProtocolFeeConfig} + */ function getProtocolFeeConfig() public view returns (ProtocolFeeConfig memory config) { return _protocolFeeConfig; } + /** + @dev See {IPatchworkProtocol-addProtocolBanker} + */ function addProtocolBanker(address addr) external onlyOwner { _protocolBankers[addr] = true; emit ProtocolBankerAdd(msg.sender, addr); } + /** + @dev See {IPatchworkProtocol-removeProtocolBanker} + */ function removeProtocolBanker(address addr) external onlyOwner { delete _protocolBankers[addr]; emit ProtocolBankerRemove(msg.sender, addr); } + /** + @dev See {IPatchworkProtocol-withdrawFromProtocol} + */ function withdrawFromProtocol(uint256 amount) external nonReentrant { if (msg.sender != owner() && _protocolBankers[msg.sender] == false) { revert NotAuthorized(msg.sender); @@ -421,6 +482,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { emit ERC1155Patch(to, originalAddress, originalTokenId, originalAccount, patchAddress, tokenId); return tokenId; } + /** @dev See {IPatchworkProtocol-patchAccount} */ @@ -451,6 +513,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { return tokenId; } + /// common to patches function _handlePatchFee(Scope storage scope, address patchAddress) private { uint256 patchFee = scope.patchFees[patchAddress]; if (msg.value != patchFee) { @@ -463,6 +526,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } } + // common to assigns function _handleAssignFee(Scope storage scope, address fragmentAddress) private { uint256 assignFee = scope.assignFees[fragmentAddress]; if (msg.value != assignFee) { diff --git a/src/PatchworkUtils.sol b/src/PatchworkUtils.sol index 9aabc60..b9a95b9 100644 --- a/src/PatchworkUtils.sol +++ b/src/PatchworkUtils.sol @@ -68,6 +68,11 @@ library PatchworkUtils { out = string(trimmedByteArray); } + /** + @notice Converts a uint16 into a 2-byte array + @param input the uint16 + @return bytes the array + */ function convertUint16ToBytes(uint16 input) public pure returns (bytes memory) { // Extract the higher and lower bytes bytes1 high = bytes1(uint8(input >> 8)); diff --git a/test/nfts/TestFragmentSingleNFT.sol b/test/nfts/TestFragmentSingleNFT.sol index 9fbce1b..93b3fee 100644 --- a/test/nfts/TestFragmentSingleNFT.sol +++ b/test/nfts/TestFragmentSingleNFT.sol @@ -95,7 +95,7 @@ contract TestFragmentSingleNFT is PatchworkFragmentSingle { return unpackMetadata(_metadataStorage[_tokenId]); } - function mint(address to, bytes memory data) external returns (uint256 tokenId){ + function mint(address to, bytes memory /*data*/) external returns (uint256 tokenId){ // Just for testing tokenId = _nextTokenId; _nextTokenId++; From de74d011ff02ba2bf946969b92c25b60c5c1370f Mon Sep 17 00:00:00 2001 From: Rob Green Date: Thu, 30 Nov 2023 11:36:26 -0800 Subject: [PATCH 28/63] Patchwork721 is ownable (#51) --- src/Patchwork721.sol | 30 ++++++++++-------------- src/PatchworkProtocol.sol | 6 ++--- test/nfts/Test1155PatchNFT.sol | 2 +- test/nfts/TestAccountPatchNFT.sol | 2 +- test/nfts/TestDynamicArrayLiteRefNFT.sol | 2 +- test/nfts/TestFragmentLiteRefNFT.sol | 2 +- test/nfts/TestFragmentSingleNFT.sol | 2 +- test/nfts/TestMultiFragmentNFT.sol | 2 +- test/nfts/TestPatchFragmentNFT.sol | 2 +- test/nfts/TestPatchLiteRefNFT.sol | 2 +- test/nfts/TestPatchNFT.sol | 2 +- test/nfts/TestPatchworkNFT.sol | 2 +- 12 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/Patchwork721.sol b/src/Patchwork721.sol index bb16840..5ad46c3 100644 --- a/src/Patchwork721.sol +++ b/src/Patchwork721.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.13; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; import "./IPatchwork721.sol"; import "./IERC4906.sol"; import "./IPatchworkProtocol.sol"; @@ -11,14 +12,11 @@ import "./IPatchworkProtocol.sol"; @dev This abstract contract defines the core functionalities for the Patchwork721. It inherits from the standard ERC721, as well as the IPatchwork721 and IERC4906 interfaces. */ -abstract contract Patchwork721 is ERC721, IPatchwork721, IERC4906 { +abstract contract Patchwork721 is ERC721, IPatchwork721, IERC4906, Ownable { /// @dev The scope name of this 721. string internal _scopeName; - /// @dev The address that denotes the owner of the contract. - address internal _owner; - /// @dev Our manager (PatchworkProtocol). address internal _manager; @@ -38,24 +36,22 @@ abstract contract Patchwork721 is ERC721, IPatchwork721, IERC4906 { mapping(uint256 => bool) internal _locks; /** - * @notice Creates a new instance of the Patchwork721 contract with the provided parameters. - * @param scopeName_ The scope name. - * @param name_ The ERC-721 name. - * @param symbol_ The ERC-721 symbol. - * @param owner_ The address that will be set as the owner. - * @param manager_ The address that will be set as the manager (PatchworkProtocol). - */ + @notice Creates a new instance of the Patchwork721 contract with the provided parameters. + @dev msg.sender will be initial owner + @param scopeName_ The scope name. + @param name_ The ERC-721 name. + @param symbol_ The ERC-721 symbol. + @param manager_ The address that will be set as the manager (PatchworkProtocol). + */ constructor( string memory scopeName_, string memory name_, string memory symbol_, - address owner_, address manager_ - ) ERC721(name_, symbol_) { + ) ERC721(name_, symbol_) Ownable() { _scopeName = scopeName_; - _owner = owner_; _manager = manager_; - } + } /** @dev See {IPatchwork721-getScopeName} @@ -94,12 +90,12 @@ abstract contract Patchwork721 is ERC721, IPatchwork721, IERC4906 { // Does msg.sender have permission to write to our top level storage? function _checkWriteAuth() internal virtual view returns (bool allow) { - return (msg.sender == _owner); + return (msg.sender == owner()); } // Does msg.sender have permission to write to this token's data? function _checkTokenWriteAuth(uint256 /*tokenId*/) internal virtual view returns (bool allow) { - return (msg.sender == _owner || msg.sender == _manager); + return (msg.sender == owner() || msg.sender == _manager); } /** diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index cce7d16..acbd904 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -15,6 +15,9 @@ pragma solidity ^0.8.13; */ +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "./IPatchwork721.sol"; import "./IPatchworkSingleAssignable.sol"; import "./IPatchworkMultiAssignable.sol"; @@ -25,9 +28,6 @@ import "./IPatchworkAccountPatch.sol"; import "./IPatchworkProtocol.sol"; import "./IPatchworkMintable.sol"; import "./IPatchworkScoped.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; /** @title Patchwork Protocol diff --git a/test/nfts/Test1155PatchNFT.sol b/test/nfts/Test1155PatchNFT.sol index 09427ab..abd965e 100644 --- a/test/nfts/Test1155PatchNFT.sol +++ b/test/nfts/Test1155PatchNFT.sol @@ -12,7 +12,7 @@ contract Test1155PatchNFT is Patchwork1155Patch { uint256 thing; } - constructor(address manager_, bool reverseEnabled_) Patchwork721("testscope", "Test1155PatchNFT", "TPLR", msg.sender, manager_) { + constructor(address manager_, bool reverseEnabled_) Patchwork721("testscope", "Test1155PatchNFT", "TPLR", manager_) { _reverseEnabled = reverseEnabled_; } diff --git a/test/nfts/TestAccountPatchNFT.sol b/test/nfts/TestAccountPatchNFT.sol index c164f22..44f593e 100644 --- a/test/nfts/TestAccountPatchNFT.sol +++ b/test/nfts/TestAccountPatchNFT.sol @@ -13,7 +13,7 @@ contract TestAccountPatchNFT is PatchworkAccountPatch { uint256 thing; } - constructor(address manager_, bool sameOwnerModel_, bool reverseEnabled_) Patchwork721("testscope", "TestAccountPatchNFT", "TPLR", msg.sender, manager_) { + constructor(address manager_, bool sameOwnerModel_, bool reverseEnabled_) Patchwork721("testscope", "TestAccountPatchNFT", "TPLR", manager_) { _sameOwnerModel = sameOwnerModel_; _reverseEnabled = reverseEnabled_; } diff --git a/test/nfts/TestDynamicArrayLiteRefNFT.sol b/test/nfts/TestDynamicArrayLiteRefNFT.sol index 85325f4..2cb6b3c 100644 --- a/test/nfts/TestDynamicArrayLiteRefNFT.sol +++ b/test/nfts/TestDynamicArrayLiteRefNFT.sol @@ -35,7 +35,7 @@ contract TestDynamicArrayLiteRefNFT is Patchwork721, PatchworkLiteRef, IPatchwor mapping(uint256 => DynamicLiteRefs) internal _dynamicLiterefStorage; // tokenId => indexed slots - constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", msg.sender, manager_) PatchworkLiteRef() { + constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", manager_) PatchworkLiteRef() { } // ERC-165 diff --git a/test/nfts/TestFragmentLiteRefNFT.sol b/test/nfts/TestFragmentLiteRefNFT.sol index 56a649e..fab9d80 100644 --- a/test/nfts/TestFragmentLiteRefNFT.sol +++ b/test/nfts/TestFragmentLiteRefNFT.sol @@ -36,7 +36,7 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef, IP bool _getAssignedToOverrideSet; address _getAssignedToOverride; - constructor (address _manager) Patchwork721("testscope", "TestFragmentLiteRef", "TFLR", msg.sender, _manager) { + constructor (address _manager) Patchwork721("testscope", "TestFragmentLiteRef", "TFLR", _manager) { } // ERC-165 diff --git a/test/nfts/TestFragmentSingleNFT.sol b/test/nfts/TestFragmentSingleNFT.sol index 93b3fee..a17ca49 100644 --- a/test/nfts/TestFragmentSingleNFT.sol +++ b/test/nfts/TestFragmentSingleNFT.sol @@ -26,7 +26,7 @@ contract TestFragmentSingleNFT is PatchworkFragmentSingle { uint256 _nextTokenId; - constructor(address manager_) Patchwork721("testscope", "TestPatchFragment", "TPLR", msg.sender, manager_) PatchworkFragmentSingle() { + constructor(address manager_) Patchwork721("testscope", "TestPatchFragment", "TPLR", manager_) PatchworkFragmentSingle() { } function schemaURI() pure external override returns (string memory) { diff --git a/test/nfts/TestMultiFragmentNFT.sol b/test/nfts/TestMultiFragmentNFT.sol index 99cea72..f43db32 100644 --- a/test/nfts/TestMultiFragmentNFT.sol +++ b/test/nfts/TestMultiFragmentNFT.sol @@ -12,7 +12,7 @@ struct TestMultiFragmentNFTMetadata { contract TestMultiFragmentNFT is PatchworkFragmentMulti, IPatchworkMintable { uint256 _nextTokenId; - constructor (address _manager) Patchwork721("testscope", "TestMultiFragmentNFT", "TFLR", msg.sender, _manager) { + constructor (address _manager) Patchwork721("testscope", "TestMultiFragmentNFT", "TFLR", _manager) { } function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { diff --git a/test/nfts/TestPatchFragmentNFT.sol b/test/nfts/TestPatchFragmentNFT.sol index adc765e..bf9ae10 100644 --- a/test/nfts/TestPatchFragmentNFT.sol +++ b/test/nfts/TestPatchFragmentNFT.sol @@ -26,7 +26,7 @@ contract TestPatchFragmentNFT is PatchworkPatch, PatchworkFragmentSingle { uint256 _nextTokenId; - constructor(address manager_) Patchwork721("testscope", "TestPatchFragment", "TPLR", msg.sender, manager_) PatchworkFragmentSingle() { + constructor(address manager_) Patchwork721("testscope", "TestPatchFragment", "TPLR", manager_) PatchworkFragmentSingle() { } // ERC-165 diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index 4cd5c3b..9cf4dab 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -27,7 +27,7 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { uint256 _nextTokenId; - constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", msg.sender, manager_) PatchworkLiteRef() { + constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", manager_) PatchworkLiteRef() { } // ERC-165 diff --git a/test/nfts/TestPatchNFT.sol b/test/nfts/TestPatchNFT.sol index b92d6f3..684d2e9 100644 --- a/test/nfts/TestPatchNFT.sol +++ b/test/nfts/TestPatchNFT.sol @@ -25,7 +25,7 @@ contract TestPatchNFT is PatchworkPatch { uint256 _nextTokenId; - constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", msg.sender, manager_) { + constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", manager_) { } function schemaURI() pure external override returns (string memory) { diff --git a/test/nfts/TestPatchworkNFT.sol b/test/nfts/TestPatchworkNFT.sol index 07ecc01..6065e13 100644 --- a/test/nfts/TestPatchworkNFT.sol +++ b/test/nfts/TestPatchworkNFT.sol @@ -12,7 +12,7 @@ contract TestPatchworkNFT is Patchwork721, IPatchworkMintable { uint256 thing; } - constructor(address manager_) Patchwork721("testscope", "TestPatchworkNFT", "TPLR", msg.sender, manager_) { + constructor(address manager_) Patchwork721("testscope", "TestPatchworkNFT", "TPLR", manager_) { } function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { From efb7dd36159f4c5191fe1ea8da44c7b70a54f6c6 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Thu, 30 Nov 2023 11:36:39 -0800 Subject: [PATCH 29/63] small bugfix in test (#52) * small bugfix in test * Set foundry to use latest solc version --- foundry.toml | 1 + test/PatchworkFragmentSingle.t.sol | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry.toml b/foundry.toml index a1baf7e..c0c4411 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,5 +5,6 @@ out = 'out' test = 'test' cache_path = 'cache' script = 'script' +solc_version = '0.8.23' # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/test/PatchworkFragmentSingle.t.sol b/test/PatchworkFragmentSingle.t.sol index 3289acb..7573f60 100644 --- a/test/PatchworkFragmentSingle.t.sol +++ b/test/PatchworkFragmentSingle.t.sol @@ -36,7 +36,6 @@ contract PatchworkFragmentSingleTest is Test { _testFragmentLiteRefNFT = new TestFragmentLiteRefNFT(address(_prot)); vm.stopPrank(); - vm.prank(_userAddress); } function testScopeName() public { From e65b6459bc0d3c0edc8edd440cf4107cea6482cb Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 1 Dec 2023 09:18:34 -0800 Subject: [PATCH 30/63] Protocol fee overrides (#53) --- src/IPatchworkProtocol.sol | 23 +++++++++++ src/PatchworkProtocol.sol | 84 ++++++++++++++++++++++++++++---------- test/Fees.t.sol | 66 ++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 22 deletions(-) diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index dd5215e..1d40052 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -248,6 +248,16 @@ interface IPatchworkProtocol { uint256 assignBp; /// assign basis points (10000 = 100%) } + /** + @notice Protocol Fee Override + */ + struct ProtocolFeeOverride { + uint256 mintBp; /// mint basis points (10000 = 100%) + uint256 patchBp; /// patch basis points (10000 = 100%) + uint256 assignBp; /// assign basis points (10000 = 100%) + bool active; /// true for present + } + /** @notice Mint configuration */ @@ -717,6 +727,19 @@ interface IPatchworkProtocol { */ function getProtocolFeeConfig() external view returns (ProtocolFeeConfig memory config); + /** + @notice Set the protocol fee override for a scope + @dev must be protocol owner or banker to call + @param config The protocol fee configuration to be set + */ + function setScopeFeeOverride(string memory scopeName, ProtocolFeeOverride memory config) external; + + /** + @notice Get the protocol fee override for a scope + @return config The current protocol fee override + */ + function getScopeFeeOverride(string memory scopeName) external view returns (ProtocolFeeOverride memory config); + /** @notice Add a banker to the protocol @dev must be protocol owner to call diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index acbd904..aa2d124 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -63,8 +63,8 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { /// Current protocol fee configuration ProtocolFeeConfig _protocolFeeConfig; - // TODO overrides? - mapping(string => ProtocolFeeConfig) _scopeFeeOverrides; // scope-based fee overrides + /// scope-based fee overrides + mapping(string => ProtocolFeeOverride) _scopeFeeOverrides; // TODO maybe not necessary uint256 public constant TRANSFER_GAS_LIMIT = 5000; @@ -304,7 +304,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { if (msg.value != config.flatFee) { revert IncorrectFeeAmount(); } - _handleMintFee(scope); + _handleMintFee(scopeName, scope); tokenId = IPatchworkMintable(mintable).mint(to, data); emit Mint(msg.sender, scopeName, to, mintable, data); } @@ -318,7 +318,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { if (msg.value != totalFee) { revert IncorrectFeeAmount(); } - _handleMintFee(scope); + _handleMintFee(scopeName, scope); tokenIds = IPatchworkMintable(mintable).mintBatch(to, data, quantity); emit MintBatch(msg.sender, scopeName, to, mintable, data, quantity); } @@ -338,10 +338,17 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } /// Common to mints - function _handleMintFee(Scope storage scope) internal { + function _handleMintFee(string memory scopeName, Scope storage scope) internal { // Account for 100% of the message value if (msg.value != 0) { - uint256 protocolFee = msg.value * _protocolFeeConfig.mintBp / 10000; + uint256 mintBp; + ProtocolFeeOverride storage feeOverride = _scopeFeeOverrides[scopeName]; + if (feeOverride.active) { + mintBp = feeOverride.mintBp; + } else { + mintBp = _protocolFeeConfig.mintBp; + } + uint256 protocolFee = msg.value * mintBp / 10000; _protocolBalance += protocolFee; scope.balance += msg.value - protocolFee; } @@ -350,10 +357,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { /** @dev See {IPatchworkProtocol-setProtocolFeeConfig} */ - function setProtocolFeeConfig(ProtocolFeeConfig memory config) public { - if (msg.sender != owner() && _protocolBankers[msg.sender] == false) { - revert NotAuthorized(msg.sender); - } + function setProtocolFeeConfig(ProtocolFeeConfig memory config) public onlyProtoOwnerBanker { _protocolFeeConfig = config; } @@ -364,6 +368,24 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { return _protocolFeeConfig; } + /** + @dev See {IPatchworkProtocol-setScopeFeeOverride} + */ + function setScopeFeeOverride(string memory scopeName, ProtocolFeeOverride memory config) public onlyProtoOwnerBanker { + if (!config.active) { + delete _scopeFeeOverrides[scopeName]; + } else { + _scopeFeeOverrides[scopeName] = config; + } + } + + /** + @dev See {IPatchworkProtocol-getScopeFeeOverride} + */ + function getScopeFeeOverride(string memory scopeName) public view returns (ProtocolFeeOverride memory config) { + return _scopeFeeOverrides[scopeName]; + } + /** @dev See {IPatchworkProtocol-addProtocolBanker} */ @@ -383,10 +405,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { /** @dev See {IPatchworkProtocol-withdrawFromProtocol} */ - function withdrawFromProtocol(uint256 amount) external nonReentrant { - if (msg.sender != owner() && _protocolBankers[msg.sender] == false) { - revert NotAuthorized(msg.sender); - } + function withdrawFromProtocol(uint256 amount) external nonReentrant onlyProtoOwnerBanker { if (amount > _protocolBalance) { revert InsufficientFunds(); } @@ -441,7 +460,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { revert NotAuthorized(msg.sender); } - _handlePatchFee(scope, patchAddress); + _handlePatchFee(scopeName, scope, patchAddress); // limit this to one unique patch (originalAddress+TokenID+patchAddress) bytes32 _hash = keccak256(abi.encodePacked(originalAddress, originalTokenId, patchAddress)); if (_uniquePatches[_hash]) { @@ -471,7 +490,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { revert NotAuthorized(msg.sender); } - _handlePatchFee(scope, patchAddress); + _handlePatchFee(scopeName, scope, patchAddress); // limit this to one unique patch (originalAddress+TokenID+patchAddress) bytes32 _hash = keccak256(abi.encodePacked(originalAddress, originalTokenId, originalAccount, patchAddress)); if (_uniquePatches[_hash]) { @@ -501,7 +520,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { revert NotAuthorized(msg.sender); } - _handlePatchFee(scope, patchAddress); + _handlePatchFee(scopeName, scope, patchAddress); // limit this to one unique patch (originalAddress+TokenID+patchAddress) bytes32 _hash = keccak256(abi.encodePacked(originalAddress, patchAddress)); if (_uniquePatches[_hash]) { @@ -514,26 +533,40 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } /// common to patches - function _handlePatchFee(Scope storage scope, address patchAddress) private { + function _handlePatchFee(string memory scopeName, Scope storage scope, address patchAddress) private { uint256 patchFee = scope.patchFees[patchAddress]; if (msg.value != patchFee) { revert IncorrectFeeAmount(); } if (msg.value > 0) { - uint256 protocolFee = msg.value * _protocolFeeConfig.patchBp / 10000; + uint256 patchBp; + ProtocolFeeOverride storage feeOverride = _scopeFeeOverrides[scopeName]; + if (feeOverride.active) { + patchBp = feeOverride.patchBp; + } else { + patchBp = _protocolFeeConfig.patchBp; + } + uint256 protocolFee = msg.value * patchBp / 10000; _protocolBalance += protocolFee; scope.balance += msg.value - protocolFee; } } // common to assigns - function _handleAssignFee(Scope storage scope, address fragmentAddress) private { + function _handleAssignFee(string memory scopeName, Scope storage scope, address fragmentAddress) private { uint256 assignFee = scope.assignFees[fragmentAddress]; if (msg.value != assignFee) { revert IncorrectFeeAmount(); } if (msg.value > 0) { - uint256 protocolFee = msg.value * _protocolFeeConfig.assignBp / 10000; + uint256 assignBp; + ProtocolFeeOverride storage feeOverride = _scopeFeeOverrides[scopeName]; + if (feeOverride.active) { + assignBp = feeOverride.assignBp; + } else { + assignBp = _protocolFeeConfig.assignBp; + } + uint256 protocolFee = msg.value * assignBp / 10000; _protocolBalance += protocolFee; scope.balance += msg.value - protocolFee; } @@ -615,7 +648,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { string memory fragmentScopeName = assignable.getScopeName(); Scope storage fragmentScope = _mustHaveScope(fragmentScopeName); _mustBeWhitelisted(fragmentScopeName, fragmentScope, fragment); - _handleAssignFee(fragmentScope, fragment); + _handleAssignFee(fragmentScopeName, fragmentScope, fragment); } if (targetScope.owner == msg.sender || targetScope.operators[msg.sender]) { // all good @@ -953,4 +986,11 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } return false; } + + modifier onlyProtoOwnerBanker() { + if (msg.sender != owner() && _protocolBankers[msg.sender] == false) { + revert NotAuthorized(msg.sender); + } + _; + } } \ No newline at end of file diff --git a/test/Fees.t.sol b/test/Fees.t.sol index 5a5dd90..6d45a75 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -318,4 +318,70 @@ contract FeesTest is Test { _prot.assign{value: 1 ether}(address(nft), n2, address(nft), n1); _prot.assign{value: _prot.getAssignFee(address(nft))}(address(nft), n2, address(nft), n1); } + + function testFeeOverrides() public { + vm.startPrank(_scopeOwner); + _prot.setScopeRules(_scopeName, false, false, true); + TestBaseNFT tBase = new TestBaseNFT(); + TestPatchLiteRefNFT t721 = new TestPatchLiteRefNFT(address(_prot)); + TestFragmentLiteRefNFT fragLr = new TestFragmentLiteRefNFT(address(_prot)); + _prot.addWhitelist(_scopeName, address(tBase)); + _prot.addWhitelist(_scopeName, address(t721)); + _prot.addWhitelist(_scopeName, address(fragLr)); + _prot.setMintConfiguration(address(fragLr), IPatchworkProtocol.MintConfig(1000000000, true)); + // Scope owner cannot set fee overrides for anyone + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); + _prot.setScopeFeeOverride(_scopeName, IPatchworkProtocol.ProtocolFeeOverride(100, 100, 100, true)); // 1% + + IPatchworkProtocol.ProtocolFeeOverride memory protFee = _prot.getScopeFeeOverride(_scopeName); + assertEq(false, protFee.active); + assertEq(0, protFee.mintBp); + assertEq(0, protFee.assignBp); + assertEq(0, protFee.patchBp); + + vm.stopPrank(); + vm.prank(_patchworkOwner); + _prot.setScopeFeeOverride(_scopeName, IPatchworkProtocol.ProtocolFeeOverride(100, 100, 100, true)); // 1% + vm.prank(_patchworkOwner); + _prot.addProtocolBanker(_user2Address); + vm.prank(_user2Address); + _prot.setScopeFeeOverride(_scopeName, IPatchworkProtocol.ProtocolFeeOverride(100, 100, 100, true)); // 1% + protFee = _prot.getScopeFeeOverride(_scopeName); + assertEq(true, protFee.active); + assertEq(100, protFee.mintBp); + assertEq(100, protFee.assignBp); + assertEq(100, protFee.patchBp); + + vm.startPrank(_scopeOwner); + _prot.mint{value: 1000000000}(_userAddress, address(fragLr), ""); + + assertEq(990000000, _prot.balanceOf(_scopeName)); + assertEq(10000000, _prot.balanceOfProtocol()); + + fragLr.registerReferenceAddress(address(fragLr)); + _prot.setAssignFee(address(fragLr), 1000000000); + uint256 n1 = fragLr.mint(_userAddress, ""); + uint256 n2 = fragLr.mint(_userAddress, ""); + _prot.assign{value: _prot.getAssignFee(address(fragLr))}(address(fragLr), n2, address(fragLr), n1); + + assertEq(1980000000, _prot.balanceOf(_scopeName)); + assertEq(20000000, _prot.balanceOfProtocol()); + + _prot.setPatchFee(address(t721), 1000000000); + uint256 tId = tBase.mint(_userAddress); + _prot.patch{value: _prot.getPatchFee(address(t721))}(_userAddress, address(tBase), tId, address(t721)); + + assertEq(2970000000, _prot.balanceOf(_scopeName)); + assertEq(30000000, _prot.balanceOfProtocol()); + + vm.stopPrank(); + vm.prank(_patchworkOwner); + _prot.setScopeFeeOverride(_scopeName, IPatchworkProtocol.ProtocolFeeOverride(0, 0, 0, false)); // 1% + protFee = _prot.getScopeFeeOverride(_scopeName); + assertEq(false, protFee.active); + assertEq(0, protFee.mintBp); + assertEq(0, protFee.assignBp); + assertEq(0, protFee.patchBp); + + } } \ No newline at end of file From 6cf8c9c622a20213d39e03a360732c417b728b15 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 1 Dec 2023 11:25:16 -0800 Subject: [PATCH 31/63] Patch burns (#54) * callbacks for burning patches * Patch burning working --- src/IPatchworkProtocol.sol | 27 ++++++++++++++++++++++++ src/Patchwork1155Patch.sol | 11 ++++++++-- src/PatchworkAccountPatch.sol | 8 ++++++-- src/PatchworkPatch.sol | 9 ++++++-- src/PatchworkProtocol.sol | 35 ++++++++++++++++++++++++++++++-- test/Patchwork1155Patch.t.sol | 6 ++++-- test/PatchworkAccountPatch.t.sol | 4 +++- test/PatchworkPatch.t.sol | 4 +++- test/PatchworkProtocol.t.sol | 10 +++++++++ 9 files changed, 102 insertions(+), 12 deletions(-) diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index 1d40052..50bc757 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -778,6 +778,15 @@ interface IPatchworkProtocol { */ function patch(address owner, address originalAddress, uint originalTokenId, address patchAddress) external payable returns (uint256 tokenId); + /** + @notice Callback for when a patch is burned + @dev can only be called from the patchAddress + @param originalAddress Address of the original 721 + @param originalTokenId Token ID of the original 721 + @param patchAddress Address of the IPatchworkPatch to mint + */ + function patchBurned(address originalAddress, uint originalTokenId, address patchAddress) external; + /** @notice Create a new 1155 patch @param originalAddress Address of the original 1155 @@ -788,6 +797,16 @@ interface IPatchworkProtocol { */ function patch1155(address to, address originalAddress, uint originalTokenId, address originalAccount, address patchAddress) external payable returns (uint256 tokenId); + /** + @notice Callback for when an 1155 patch is burned + @dev can only be called from the patchAddress + @param originalAddress Address of the original 1155 + @param originalTokenId Token ID of the original 1155 + @param originalAccount Address of the account to patch + @param patchAddress Address of the IPatchworkPatch to mint + */ + function patchBurned1155(address originalAddress, uint originalTokenId, address originalAccount, address patchAddress) external; + /** @notice Create a new account patch @param owner The owner of the patch @@ -797,6 +816,14 @@ interface IPatchworkProtocol { */ function patchAccount(address owner, address originalAddress, address patchAddress) external payable returns (uint256 tokenId); + /** + @notice Callback for when an account patch is burned + @dev can only be called from the patchAddress + @param originalAddress Address of the original 1155 + @param patchAddress Address of the IPatchworkPatch to mint + */ + function patchBurnedAccount(address originalAddress, address patchAddress) external; + /** @notice Assigns a relation to have an IPatchworkLiteRef form a LiteRef to a IPatchworkAssignable @param fragment The IPatchworkAssignable address to assign diff --git a/src/Patchwork1155Patch.sol b/src/Patchwork1155Patch.sol index d212e20..3591182 100644 --- a/src/Patchwork1155Patch.sol +++ b/src/Patchwork1155Patch.sol @@ -63,7 +63,14 @@ abstract contract Patchwork1155Patch is Patchwork721, IPatchwork1155Patch { /** @dev See {ERC721-_burn} */ - function _burn(uint256 /*tokenId*/) internal virtual override { - revert IPatchworkProtocol.UnsupportedOperation(); + function _burn(uint256 tokenId) internal virtual override { + PatchCanonical storage canonical = _patchedAddresses[tokenId]; + address originalAddress = canonical.addr; + uint256 originalTokenId = canonical.tokenId; + address account = canonical.account; + IPatchworkProtocol(_manager).patchBurned1155(originalAddress, originalTokenId, account, address(this)); + delete _patchedAddresses[tokenId]; + delete _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId, account))]; + super._burn(tokenId); } } \ No newline at end of file diff --git a/src/PatchworkAccountPatch.sol b/src/PatchworkAccountPatch.sol index cbc680c..04f47b6 100644 --- a/src/PatchworkAccountPatch.sol +++ b/src/PatchworkAccountPatch.sol @@ -57,8 +57,12 @@ abstract contract PatchworkAccountPatch is Patchwork721, IPatchworkAccountPatch /** @dev See {ERC721-_burn} */ - function _burn(uint256 /*tokenId*/) internal virtual override { - revert IPatchworkProtocol.UnsupportedOperation(); + function _burn(uint256 tokenId) internal virtual override { + address originalAddress = _patchedAddresses[tokenId]; + IPatchworkProtocol(_manager).patchBurnedAccount(originalAddress, address(this)); + delete _patchedAddresses[tokenId]; + delete _patchedAddressesRev[originalAddress]; + super._burn(tokenId); } } \ No newline at end of file diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index 3398433..3c74d4c 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -108,7 +108,12 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { /** @dev See {ERC721-_burn} */ - function _burn(uint256 /*tokenId*/) internal virtual override { - revert IPatchworkProtocol.UnsupportedOperation(); + function _burn(uint256 tokenId) internal virtual override { + address originalAddress = _patchedAddresses[tokenId]; + uint256 originalTokenId = _patchedTokenIds[tokenId]; + IPatchworkProtocol(_manager).patchBurned(originalAddress, originalTokenId, address(this)); + delete _patchedAddresses[tokenId]; + delete _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))]; + super._burn(tokenId); } } \ No newline at end of file diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index aa2d124..3ac5448 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -472,7 +472,15 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { return tokenId; } - /** + /** + @dev See {IPatchworkProtocol-patchBurned} + */ + function patchBurned(address originalAddress, uint originalTokenId, address patchAddress) external onlyFrom(patchAddress) { + bytes32 _hash = keccak256(abi.encodePacked(originalAddress, originalTokenId, patchAddress)); + delete _uniquePatches[_hash]; + } + + /** @dev See {IPatchworkProtocol-patch1155} */ function patch1155(address to, address originalAddress, uint originalTokenId, address originalAccount, address patchAddress) external payable returns (uint256 tokenId) { @@ -502,6 +510,14 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { return tokenId; } + /** + @dev See {IPatchworkProtocol-patchBurned1155} + */ + function patchBurned1155(address originalAddress, uint originalTokenId, address originalAccount, address patchAddress) external onlyFrom(patchAddress) { + bytes32 _hash = keccak256(abi.encodePacked(originalAddress, originalTokenId, originalAccount, patchAddress)); + delete _uniquePatches[_hash]; + } + /** @dev See {IPatchworkProtocol-patchAccount} */ @@ -532,6 +548,14 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { return tokenId; } + /** + @dev See {IPatchworkProtocol-patchBurnedAccount} + */ + function patchBurnedAccount(address originalAddress, address patchAddress) external onlyFrom(patchAddress) { + bytes32 _hash = keccak256(abi.encodePacked(originalAddress, patchAddress)); + delete _uniquePatches[_hash]; + } + /// common to patches function _handlePatchFee(string memory scopeName, Scope storage scope, address patchAddress) private { uint256 patchFee = scope.patchFees[patchAddress]; @@ -986,11 +1010,18 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } return false; } - + modifier onlyProtoOwnerBanker() { if (msg.sender != owner() && _protocolBankers[msg.sender] == false) { revert NotAuthorized(msg.sender); } _; } + + modifier onlyFrom(address addr) { + if (msg.sender != addr) { + revert NotAuthorized(msg.sender); + } + _; + } } \ No newline at end of file diff --git a/test/Patchwork1155Patch.t.sol b/test/Patchwork1155Patch.t.sol index ca81326..6ba271d 100644 --- a/test/Patchwork1155Patch.t.sol +++ b/test/Patchwork1155Patch.t.sol @@ -114,8 +114,10 @@ contract Patchwork1155PatchTest is Test { Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot), false); TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedOperation.selector)); - test1155PatchNFT.burn(b); + uint256 pId = _prot.patch1155(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); + test1155PatchNFT.burn(pId); + // Should be able to re-patch now + pId = _prot.patch1155(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); } function testReverseLookups() public { diff --git a/test/PatchworkAccountPatch.t.sol b/test/PatchworkAccountPatch.t.sol index b3554c4..cb39ab4 100644 --- a/test/PatchworkAccountPatch.t.sol +++ b/test/PatchworkAccountPatch.t.sol @@ -71,8 +71,10 @@ contract PatchworkAccountPatchTest is Test { vm.prank(_userAddress); testAccountPatchNFT.transferFrom(_userAddress, address(55), tokenId); vm.prank(address(55)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedOperation.selector)); testAccountPatchNFT.burn(tokenId); + // Should be able to re-patch now + vm.prank(_scopeOwner); + tokenId = _prot.patchAccount(_userAddress, _user2Address, address(testAccountPatchNFT)); } function testAccountPatchSameOwner() public { diff --git a/test/PatchworkPatch.t.sol b/test/PatchworkPatch.t.sol index 091b892..7918306 100644 --- a/test/PatchworkPatch.t.sol +++ b/test/PatchworkPatch.t.sol @@ -70,8 +70,10 @@ contract PatchworkPatchTest is Test { uint256 baseTokenId = _testBaseNFT.mint(_userAddress); vm.prank(_scopeOwner); uint256 patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedOperation.selector)); _testPatchLiteRefNFT.burn(patchTokenId); + // Should be able to re-patch now + vm.prank(_scopeOwner); + patchTokenId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); } function testOtherOwnerDisallowed() public { diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index f317e97..aedc84b 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -901,4 +901,14 @@ contract PatchworkProtocolTest is Test { (uint64 lr2,) = _testFragmentLiteRefNFT.getLiteReference(address(testFrag2), frag2); assertEq(lr1, lr2); } + + function testBurn() public { + // *burned* can only be called from the patch burning it + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _prot.patchBurned(address(1), 1, address(2)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _prot.patchBurned1155(address(1), 1, address(3), address(2)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _prot.patchBurnedAccount(address(1), address(2)); + } } \ No newline at end of file From 51648f90c65695b3d60acd96dab02041d630561a Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 1 Dec 2023 11:26:53 -0800 Subject: [PATCH 32/63] Target scope unassign (#55) --- src/PatchworkProtocol.sol | 8 ++++---- test/PatchworkFragmentMulti.t.sol | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 3ac5448..f139acf 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -771,10 +771,10 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { */ function _unassignMultiCommon(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool isDirect, uint256 targetMetadataId) private { IPatchworkMultiAssignable assignable = IPatchworkMultiAssignable(fragment); - string memory scopeName = assignable.getScopeName(); if (!assignable.isAssignedTo(fragmentTokenId, target, targetTokenId)) { revert FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); } + string memory scopeName = IPatchworkScoped(target).getScopeName(); _doUnassign(fragment, fragmentTokenId, target, targetTokenId, isDirect, targetMetadataId, scopeName); assignable.unassign(fragmentTokenId, target, targetTokenId); } @@ -798,11 +798,11 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { */ function _unassignSingleCommon(address fragment, uint fragmentTokenId, bool isDirect, uint256 targetMetadataId) private { IPatchworkSingleAssignable assignable = IPatchworkSingleAssignable(fragment); - string memory scopeName = assignable.getScopeName(); (address target, uint256 targetTokenId) = assignable.getAssignedTo(fragmentTokenId); if (target == address(0)) { revert FragmentNotAssigned(fragment, fragmentTokenId); } + string memory scopeName = IPatchworkScoped(target).getScopeName(); _doUnassign(fragment, fragmentTokenId, target, targetTokenId, isDirect, targetMetadataId, scopeName); assignable.unassign(fragmentTokenId); } @@ -813,7 +813,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @param fragmentTokenId the IPatchworkAssignable's tokenId @param target the IPatchworkLiteRef target's address @param targetTokenId the IPatchworkLiteRef target's tokenId - @param scopeName the name of the assignable's scope + @param scopeName the name of the target's scope */ function _doUnassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool direct, uint256 targetMetadataId, string memory scopeName) private { Scope storage scope = _mustHaveScope(scopeName); @@ -842,7 +842,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { IPatchworkLiteRef(target).removeReference(targetTokenId, ref); } - emit Unassign(IERC721(target).ownerOf(targetTokenId), fragment, fragmentTokenId, target, targetTokenId); + emit Unassign(IERC721(fragment).ownerOf(fragmentTokenId), fragment, fragmentTokenId, target, targetTokenId); } /** diff --git a/test/PatchworkFragmentMulti.t.sol b/test/PatchworkFragmentMulti.t.sol index 642d4e9..7ebe59a 100644 --- a/test/PatchworkFragmentMulti.t.sol +++ b/test/PatchworkFragmentMulti.t.sol @@ -194,5 +194,13 @@ contract PatchworkFragmentMultiTest is Test { _testFragmentLiteRefNFT.registerReferenceAddress(address(multi)); uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); _prot.assign(address(multi), m1, address(_testFragmentLiteRefNFT), lr1); + vm.stopPrank(); + // fragment scope can not unassign + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, publicScopeOwner)); + vm.prank(publicScopeOwner); + _prot.unassign(address(multi), m1, address(_testFragmentLiteRefNFT), lr1); + // target scope can unassign + vm.prank(_scopeOwner); + _prot.unassign(address(multi), m1, address(_testFragmentLiteRefNFT), lr1); } } \ No newline at end of file From 93d146bb122922271d42a9d13def7fb2ffafba50 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Thu, 4 Jan 2024 17:51:22 -0800 Subject: [PATCH 33/63] Fixed out of bounds array access (#56) * Fixed out of bounds array access * keep the comment --- src/PatchworkFragmentMulti.sol | 4 +++- test/PatchworkFragmentMulti.t.sol | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/PatchworkFragmentMulti.sol b/src/PatchworkFragmentMulti.sol index 0b94a27..a9a0563 100644 --- a/src/PatchworkFragmentMulti.sol +++ b/src/PatchworkFragmentMulti.sol @@ -64,7 +64,9 @@ abstract contract PatchworkFragmentMulti is Patchwork721, IPatchworkMultiAssigna Assignment[] storage assignments = store.assignments; if (assignments.length > 1) { // move the last element of the array into this index - assignments[index] = assignments[assignments.length-1]; + Assignment storage a = assignments[assignments.length-1]; + assignments[index] = a; + store.index[keccak256(abi.encodePacked(a.tokenAddr, a.tokenId))] = index; } // shorten the array by 1 assignments.pop(); diff --git a/test/PatchworkFragmentMulti.t.sol b/test/PatchworkFragmentMulti.t.sol index 7ebe59a..761f51b 100644 --- a/test/PatchworkFragmentMulti.t.sol +++ b/test/PatchworkFragmentMulti.t.sol @@ -61,6 +61,7 @@ contract PatchworkFragmentMultiTest is Test { uint256 lr1 = _testFragmentLiteRefNFT.mint(_userAddress, ""); uint256 lr2 = _testFragmentLiteRefNFT.mint(_userAddress, ""); uint256 lr3 = _testFragmentLiteRefNFT.mint(_userAddress, ""); + uint256 lr4 = _testFragmentLiteRefNFT.mint(_userAddress, ""); // must be registered vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(_testMultiNFT))); _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr1); @@ -72,6 +73,8 @@ contract PatchworkFragmentMultiTest is Test { assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr2)); _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr3); assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr3)); + _prot.assign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr4); + assertTrue(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr4)); assertEq(_testMultiNFT.ownerOf(m1), _user2Address); // don't allow duplicate vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentAlreadyAssigned.selector, address(_testMultiNFT), m1)); @@ -98,6 +101,8 @@ contract PatchworkFragmentMultiTest is Test { assertFalse(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr1)); _prot.unassign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr3); assertFalse(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr3)); + _prot.unassign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr4); + assertFalse(_testMultiNFT.isAssignedTo(m1, address(_testFragmentLiteRefNFT), lr4)); // not assigned vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssignedToTarget.selector, address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2)); _prot.unassign(address(_testMultiNFT), m1, address(_testFragmentLiteRefNFT), lr2); From c4880f652d5923d364eb75ccab3cd61918cd47cb Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 5 Jan 2024 12:34:56 -0800 Subject: [PATCH 34/63] ERC165 optimizations (#57) * ERC165 optimizations - tests not fixed yet * fixing... * fixed * spacing --- src/IPatchworkProtocol.sol | 7 +++ src/PatchworkProtocol.sol | 115 +++++++++++++++++------------------ test/Fees.t.sol | 22 +++---- test/PatchworkProtocol.t.sol | 3 +- 4 files changed, 74 insertions(+), 73 deletions(-) diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index 50bc757..aca4c88 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -933,4 +933,11 @@ interface IPatchworkProtocol { @param tokenId The ID of the token whose ownership tree needs to be updated */ function updateOwnershipTree(address addr, uint256 tokenId) external; + + /** + @notice Clear supported interface memoization for the msg.sender + @dev use this if you use an upgradeable contract that may add or remove ERC165 supportsInterface signatures + @param sig The interfaceId of the type to clear + */ + function clearSupportedInterface(bytes4 sig) external; } \ No newline at end of file diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index f139acf..28c42aa 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -42,29 +42,31 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @notice unique references @dev A hash of target + targetTokenId + literef provides uniqueness */ - mapping(bytes32 => bool) _liteRefs; + mapping(bytes32 => bool) private _liteRefs; /** @notice unique patches @dev Hash of the patch mapped to a boolean indicating its uniqueness */ - mapping(bytes32 => bool) _uniquePatches; + mapping(bytes32 => bool) private _uniquePatches; /// Balance of the protocol - uint256 _protocolBalance; + uint256 private _protocolBalance; /** @notice protocol bankers @dev Map of addresses authorized to set fees and withdraw funds for the protocol @dev Does not allow for scope balance withdrawl */ - mapping(address => bool) _protocolBankers; + mapping(address => bool) private _protocolBankers; /// Current protocol fee configuration - ProtocolFeeConfig _protocolFeeConfig; + ProtocolFeeConfig private _protocolFeeConfig; /// scope-based fee overrides - mapping(string => ProtocolFeeOverride) _scopeFeeOverrides; + mapping(string => ProtocolFeeOverride) private _scopeFeeOverrides; + + mapping(bytes32 => uint8) private _supportedInterfaceCache; // TODO maybe not necessary uint256 public constant TRANSFER_GAS_LIMIT = 5000; @@ -173,9 +175,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-setMintConfiguration} */ function setMintConfiguration(address addr, MintConfig memory config) public { - if (!IERC165(addr).supportsInterface(type(IPatchworkMintable).interfaceId)) { - revert UnsupportedContract(); - } IPatchworkMintable mintable = IPatchworkMintable(addr); string memory scopeName = mintable.getScopeName(); Scope storage scope = _mustHaveScope(scopeName); @@ -189,9 +188,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-getMintConfiguration} */ function getMintConfiguration(address addr) public view returns (MintConfig memory config) { - if (!IERC165(addr).supportsInterface(type(IPatchworkMintable).interfaceId)) { - revert UnsupportedContract(); - } Scope storage scope = _mustHaveScope(IPatchworkMintable(addr).getScopeName()); return scope.mintConfigurations[addr]; } @@ -200,9 +196,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-setPatchFee} */ function setPatchFee(address addr, uint256 baseFee) public { - if (!IERC165(addr).supportsInterface(type(IPatchworkScoped).interfaceId)) { - revert UnsupportedContract(); - } string memory scopeName = IPatchworkScoped(addr).getScopeName(); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, addr); @@ -214,9 +207,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-getPatchFee} */ function getPatchFee(address addr) public view returns (uint256 baseFee) { - if (!IERC165(addr).supportsInterface(type(IPatchworkScoped).interfaceId)) { - revert UnsupportedContract(); - } Scope storage scope = _mustHaveScope(IPatchworkScoped(addr).getScopeName()); return scope.patchFees[addr]; } @@ -225,9 +215,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-setAssignFee} */ function setAssignFee(address fragmentAddress, uint256 baseFee) public { - if (!IERC165(fragmentAddress).supportsInterface(type(IPatchworkScoped).interfaceId)) { - revert UnsupportedContract(); - } string memory scopeName = IPatchworkScoped(fragmentAddress).getScopeName(); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, fragmentAddress); @@ -239,9 +226,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-getAssignFee} */ function getAssignFee(address fragmentAddress) public view returns (uint256 baseFee) { - if (!IERC165(fragmentAddress).supportsInterface(type(IPatchworkScoped).interfaceId)) { - revert UnsupportedContract(); - } Scope storage scope = _mustHaveScope(IPatchworkScoped(fragmentAddress).getScopeName()); return scope.assignFees[fragmentAddress]; } @@ -325,9 +309,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { /// Common to mints function _setupMint(address mintable) internal view returns (MintConfig memory config, string memory scopeName, Scope storage scope) { - if (!IERC165(mintable).supportsInterface(type(IPatchworkMintable).interfaceId)) { - revert UnsupportedContract(); - } scopeName = IPatchworkMintable(mintable).getScopeName(); scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, mintable); @@ -446,9 +427,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-patch} */ function patch(address owner, address originalAddress, uint originalTokenId, address patchAddress) external payable returns (uint256 tokenId) { - if (!IERC165(patchAddress).supportsInterface(type(IPatchworkPatch).interfaceId)) { - revert UnsupportedContract(); - } IPatchworkPatch patch_ = IPatchworkPatch(patchAddress); string memory scopeName = patch_.getScopeName(); Scope storage scope = _mustHaveScope(scopeName); @@ -484,9 +462,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-patch1155} */ function patch1155(address to, address originalAddress, uint originalTokenId, address originalAccount, address patchAddress) external payable returns (uint256 tokenId) { - if (!IERC165(patchAddress).supportsInterface(type(IPatchwork1155Patch).interfaceId)) { - revert UnsupportedContract(); - } IPatchwork1155Patch patch_ = IPatchwork1155Patch(patchAddress); string memory scopeName = patch_.getScopeName(); Scope storage scope = _mustHaveScope(scopeName); @@ -522,9 +497,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-patchAccount} */ function patchAccount(address owner, address originalAddress, address patchAddress) external payable returns (uint256 tokenId) { - if (!IERC165(patchAddress).supportsInterface(type(IPatchworkAccountPatch).interfaceId)) { - revert UnsupportedContract(); - } IPatchworkAccountPatch patch_ = IPatchworkAccountPatch(patchAddress); string memory scopeName = patch_.getScopeName(); Scope storage scope = _mustHaveScope(scopeName); @@ -731,13 +703,13 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev Common function to handle unassignments. */ function _unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool isDirect, uint256 targetMetadataId) private { - if (IERC165(fragment).supportsInterface(type(IPatchworkMultiAssignable).interfaceId)) { + if (_supportsInterface(fragment, type(IPatchworkMultiAssignable).interfaceId)) { if (isDirect) { unassignMulti(fragment, fragmentTokenId, target, targetTokenId, targetMetadataId); } else { unassignMulti(fragment, fragmentTokenId, target, targetTokenId); } - } else if (IERC165(fragment).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { + } else if (_supportsInterface(fragment, type(IPatchworkSingleAssignable).interfaceId)) { (address _target, uint256 _targetTokenId) = IPatchworkSingleAssignable(fragment).getAssignedTo(fragmentTokenId); if (target != _target || _targetTokenId != targetTokenId) { revert FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); @@ -850,22 +822,22 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { */ function applyTransfer(address from, address to, uint256 tokenId) public { address nft = msg.sender; - if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { + if (_supportsInterface(nft, type(IPatchworkSingleAssignable).interfaceId)) { IPatchworkSingleAssignable assignable = IPatchworkSingleAssignable(nft); (address addr,) = assignable.getAssignedTo(tokenId); if (addr != address(0)) { revert TransferBlockedByAssignment(nft, tokenId); } } - if (IERC165(nft).supportsInterface(type(IPatchworkPatch).interfaceId)) { + if (_supportsInterface(nft, type(IPatchworkPatch).interfaceId)) { revert TransferNotAllowed(nft, tokenId); } - if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { + if (_supportsInterface(nft, type(IPatchwork721).interfaceId)) { if (IPatchwork721(nft).locked(tokenId)) { revert Locked(nft, tokenId); } } - if (IERC165(nft).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { + if (_supportsInterface(nft, type(IPatchworkLiteRef).interfaceId)) { (address[] memory addresses, uint256[] memory tokenIds) = IPatchworkLiteRef(nft).loadAllStaticReferences(tokenId); for (uint i = 0; i < addresses.length; i++) { if (addresses[i] != address(0)) { @@ -876,16 +848,17 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } function _applyAssignedTransfer(address nft, address from, address to, uint256 tokenId, address assignedTo_, uint256 assignedToTokenId_) private { - if (!IERC165(nft).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { - revert NotPatchworkAssignable(nft); - } - (address assignedTo, uint256 assignedToTokenId) = IPatchworkSingleAssignable(nft).getAssignedTo(tokenId); - // 2-way Check the assignment to prevent spoofing - if (assignedTo_ != assignedTo || assignedToTokenId_ != assignedToTokenId) { - revert DataIntegrityError(assignedTo_, assignedToTokenId_, assignedTo, assignedToTokenId); + if (_supportsInterface(nft, type(IPatchworkSingleAssignable).interfaceId)) { + IPatchworkSingleAssignable singleAssignable = IPatchworkSingleAssignable(nft); + (address assignedTo, uint256 assignedToTokenId) = singleAssignable.getAssignedTo(tokenId); + // 2-way Check the assignment to prevent spoofing + if (assignedTo_ != assignedTo || assignedToTokenId_ != assignedToTokenId) { + revert DataIntegrityError(assignedTo_, assignedToTokenId_, assignedTo, assignedToTokenId); + } + singleAssignable.onAssignedTransfer(from, to, tokenId); } - IPatchworkSingleAssignable(nft).onAssignedTransfer(from, to, tokenId); - if (IERC165(nft).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { + + if (_supportsInterface(nft, type(IPatchworkLiteRef).interfaceId)) { address nft_ = nft; // local variable prevents optimizer stack issue in v0.8.18 (address[] memory addresses, uint256[] memory tokenIds) = IPatchworkLiteRef(nft).loadAllStaticReferences(tokenId); for (uint i = 0; i < addresses.length; i++) { @@ -900,7 +873,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-updateOwnershipTree} */ function updateOwnershipTree(address addr, uint256 tokenId) public { - if (IERC165(addr).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { + if (_supportsInterface(addr, type(IPatchworkLiteRef).interfaceId)) { (address[] memory addresses, uint256[] memory tokenIds) = IPatchworkLiteRef(addr).loadAllStaticReferences(tokenId); for (uint i = 0; i < addresses.length; i++) { if (addresses[i] != address(0)) { @@ -908,9 +881,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } } } - if (IERC165(addr).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { + if (_supportsInterface(addr, type(IPatchworkSingleAssignable).interfaceId)) { IPatchworkSingleAssignable(addr).updateOwnership(tokenId); - } else if (IERC165(addr).supportsInterface(type(IPatchworkPatch).interfaceId)) { + } else if (_supportsInterface(addr, type(IPatchworkPatch).interfaceId)) { IPatchworkPatch(addr).updateOwnership(tokenId); } } @@ -981,12 +954,12 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @param tokenId the tokenId of nft @return frozen if the nft or an owner up the tree is frozen */ - function _isFrozen(address nft, uint256 tokenId) private view returns (bool frozen) { - if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { + function _isFrozen(address nft, uint256 tokenId) private returns (bool frozen) { + if (_supportsInterface(nft, type(IPatchwork721).interfaceId)) { if (IPatchwork721(nft).frozen(tokenId)) { return true; } - if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { + if (_supportsInterface(nft, type(IPatchworkSingleAssignable).interfaceId)) { (address assignedAddr, uint256 assignedTokenId) = IPatchworkSingleAssignable(nft).getAssignedTo(tokenId); if (assignedAddr != address(0)) { return _isFrozen(assignedAddr, assignedTokenId); @@ -1002,15 +975,37 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @param tokenId the tokenId of nft @return locked if the nft is locked */ - function _isLocked(address nft, uint256 tokenId) private view returns (bool locked) { - if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { + function _isLocked(address nft, uint256 tokenId) private returns (bool locked) { + if (_supportsInterface(nft, type(IPatchwork721).interfaceId)) { if (IPatchwork721(nft).locked(tokenId)) { return true; } } return false; } - + + function _supportsInterface(address addr, bytes4 sig) private returns (bool ret) { + bytes32 _hash = keccak256(abi.encodePacked(addr, sig)); + uint8 support = _supportedInterfaceCache[_hash]; + if (support == 1) { + return true; + } else if (support == 2) { + return false; + } else { + ret = IERC165(addr).supportsInterface(sig); + if (ret) { + _supportedInterfaceCache[_hash] = 1; + } else { + _supportedInterfaceCache[_hash] = 2; + } + return ret; + } + } + + function clearSupportedInterface(bytes4 sig) external { + delete _supportedInterfaceCache[keccak256(abi.encodePacked(msg.sender, sig))]; + } + modifier onlyProtoOwnerBanker() { if (msg.sender != owner() && _protocolBankers[msg.sender] == false) { revert NotAuthorized(msg.sender); diff --git a/test/Fees.t.sol b/test/Fees.t.sol index 6d45a75..f1fd940 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -147,21 +147,21 @@ contract FeesTest is Test { function testUnsupportedContracts() public { vm.startPrank(_scopeOwner); TestBaseNFT tBase = new TestBaseNFT(); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + vm.expectRevert(); _prot.setMintConfiguration(address(tBase), IPatchworkProtocol.MintConfig(1000000000, true)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + vm.expectRevert(); _prot.getMintConfiguration(address(tBase)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + vm.expectRevert(); _prot.setPatchFee(address(tBase), 1); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + vm.expectRevert(); _prot.getPatchFee(address(tBase)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + vm.expectRevert(); _prot.setAssignFee(address(tBase), 1); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + vm.expectRevert(); _prot.getAssignFee(address(tBase)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + vm.expectRevert(); _prot.mint(_userAddress, address(tBase), ""); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + vm.expectRevert(); _prot.mintBatch(_userAddress, address(tBase), "", 5); } @@ -269,11 +269,11 @@ contract FeesTest is Test { // patch wrong types vm.startPrank(_scopeOwner); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + vm.expectRevert(); _prot.patch(_userAddress, address(tBase), 2, address(t1155)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + vm.expectRevert(); _prot.patch1155(_userAddress, address(tBase1155), 3, address(0), address(t721)); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); + vm.expectRevert(); _prot.patchAccount(_userAddress, _user2Address, address(t721)); } diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index aedc84b..10d5e0c 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -875,9 +875,8 @@ contract PatchworkProtocolTest is Test { _testFragmentLiteRefNFT.registerReferenceAddress(address(_testBaseNFT)); (uint64 ref, ) = _testFragmentLiteRefNFT.getLiteReference(address(_testBaseNFT), _testBaseNFTTokenId); _testFragmentLiteRefNFT.addReference(fragment1, ref); - // Should revert with data integrity error + // Will not revert, will just do nothing vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotPatchworkAssignable.selector, address(_testBaseNFT))); vm.prank(_userAddress); _testFragmentLiteRefNFT.transferFrom(_userAddress, _user2Address, fragment1); } From 851b09bb566e24d0f4caf2473233c0909e8f3ac3 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 5 Jan 2024 18:25:38 -0800 Subject: [PATCH 35/63] all calls to getScopeName are memoized (#58) --- src/PatchworkProtocol.sol | 69 ++++++++++++++++++++++++++++-------- test/PatchworkProtocol.t.sol | 3 +- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 28c42aa..a5f8810 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -66,8 +66,12 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { /// scope-based fee overrides mapping(string => ProtocolFeeOverride) private _scopeFeeOverrides; + /// Supported interface cache mapping(bytes32 => uint8) private _supportedInterfaceCache; + /// Scope name cache + mapping(address => string) private _scopeNameCache; + // TODO maybe not necessary uint256 public constant TRANSFER_GAS_LIMIT = 5000; @@ -175,8 +179,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-setMintConfiguration} */ function setMintConfiguration(address addr, MintConfig memory config) public { - IPatchworkMintable mintable = IPatchworkMintable(addr); - string memory scopeName = mintable.getScopeName(); + string memory scopeName = _getScopeName(addr); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, addr); _mustBeOwnerOrOperator(scope); @@ -188,7 +191,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-getMintConfiguration} */ function getMintConfiguration(address addr) public view returns (MintConfig memory config) { - Scope storage scope = _mustHaveScope(IPatchworkMintable(addr).getScopeName()); + Scope storage scope = _mustHaveScope(_getScopeNameViewOnly(addr)); return scope.mintConfigurations[addr]; } @@ -196,7 +199,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-setPatchFee} */ function setPatchFee(address addr, uint256 baseFee) public { - string memory scopeName = IPatchworkScoped(addr).getScopeName(); + string memory scopeName = _getScopeName(addr); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, addr); _mustBeOwnerOrOperator(scope); @@ -207,7 +210,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-getPatchFee} */ function getPatchFee(address addr) public view returns (uint256 baseFee) { - Scope storage scope = _mustHaveScope(IPatchworkScoped(addr).getScopeName()); + Scope storage scope = _mustHaveScope(_getScopeNameViewOnly(addr)); return scope.patchFees[addr]; } @@ -215,7 +218,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-setAssignFee} */ function setAssignFee(address fragmentAddress, uint256 baseFee) public { - string memory scopeName = IPatchworkScoped(fragmentAddress).getScopeName(); + string memory scopeName = _getScopeName(fragmentAddress); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, fragmentAddress); _mustBeOwnerOrOperator(scope); @@ -226,7 +229,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-getAssignFee} */ function getAssignFee(address fragmentAddress) public view returns (uint256 baseFee) { - Scope storage scope = _mustHaveScope(IPatchworkScoped(fragmentAddress).getScopeName()); + Scope storage scope = _mustHaveScope(_getScopeNameViewOnly(fragmentAddress)); return scope.assignFees[fragmentAddress]; } @@ -309,7 +312,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { /// Common to mints function _setupMint(address mintable) internal view returns (MintConfig memory config, string memory scopeName, Scope storage scope) { - scopeName = IPatchworkMintable(mintable).getScopeName(); + scopeName = _getScopeNameViewOnly(mintable); scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, mintable); config = scope.mintConfigurations[mintable]; @@ -428,7 +431,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { */ function patch(address owner, address originalAddress, uint originalTokenId, address patchAddress) external payable returns (uint256 tokenId) { IPatchworkPatch patch_ = IPatchworkPatch(patchAddress); - string memory scopeName = patch_.getScopeName(); + string memory scopeName = _getScopeName(patchAddress); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, patchAddress); if (scope.owner == msg.sender || scope.operators[msg.sender]) { @@ -463,7 +466,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { */ function patch1155(address to, address originalAddress, uint originalTokenId, address originalAccount, address patchAddress) external payable returns (uint256 tokenId) { IPatchwork1155Patch patch_ = IPatchwork1155Patch(patchAddress); - string memory scopeName = patch_.getScopeName(); + string memory scopeName = _getScopeName(patchAddress); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, patchAddress); if (scope.owner == msg.sender || scope.operators[msg.sender]) { @@ -498,7 +501,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { */ function patchAccount(address owner, address originalAddress, address patchAddress) external payable returns (uint256 tokenId) { IPatchworkAccountPatch patch_ = IPatchworkAccountPatch(patchAddress); - string memory scopeName = patch_.getScopeName(); + string memory scopeName = _getScopeName(patchAddress); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, patchAddress); if (scope.owner == msg.sender || scope.operators[msg.sender]) { @@ -636,12 +639,12 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { revert Locked(fragment, fragmentTokenId); } // Use the target's scope for general permission and check the fragment for detailed permissions - string memory targetScopeName = IPatchwork721(target).getScopeName(); + string memory targetScopeName = _getScopeName(target); Scope storage targetScope = _mustHaveScope(targetScopeName); _mustBeWhitelisted(targetScopeName, targetScope, target); { // Whitelist check, these variables do not need to stay in the function level stack - string memory fragmentScopeName = assignable.getScopeName(); + string memory fragmentScopeName = _getScopeName(fragment); Scope storage fragmentScope = _mustHaveScope(fragmentScopeName); _mustBeWhitelisted(fragmentScopeName, fragmentScope, fragment); _handleAssignFee(fragmentScopeName, fragmentScope, fragment); @@ -746,7 +749,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { if (!assignable.isAssignedTo(fragmentTokenId, target, targetTokenId)) { revert FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); } - string memory scopeName = IPatchworkScoped(target).getScopeName(); + string memory scopeName = _getScopeName(target); _doUnassign(fragment, fragmentTokenId, target, targetTokenId, isDirect, targetMetadataId, scopeName); assignable.unassign(fragmentTokenId, target, targetTokenId); } @@ -774,7 +777,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { if (target == address(0)) { revert FragmentNotAssigned(fragment, fragmentTokenId); } - string memory scopeName = IPatchworkScoped(target).getScopeName(); + string memory scopeName = _getScopeName(target); _doUnassign(fragment, fragmentTokenId, target, targetTokenId, isDirect, targetMetadataId, scopeName); assignable.unassign(fragmentTokenId); } @@ -984,6 +987,13 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { return false; } + /** + @notice Memoizing wrapper for IERC165 supportsInterface() + @dev use clearSupportedInterface(sig) from the addr to clear if supported interfaces changes + @param addr Address to check + @param sig Signature to check at address + @return ret return value of IERC165(addr).supportsInterface(sig) + */ function _supportsInterface(address addr, bytes4 sig) private returns (bool ret) { bytes32 _hash = keccak256(abi.encodePacked(addr, sig)); uint8 support = _supportedInterfaceCache[_hash]; @@ -1002,10 +1012,39 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } } + /** + @dev See {IPatchworkProtocol-clearSupportedInterface} + */ function clearSupportedInterface(bytes4 sig) external { delete _supportedInterfaceCache[keccak256(abi.encodePacked(msg.sender, sig))]; } + /** + @notice Memoizing wrapper for IPatchworkScoped.getScopeName() + @param addr Address to check + @return scopeName return value of IPatchworkScoped(addr).getScopeName() + */ + function _getScopeName(address addr) private returns (string memory scopeName) { + scopeName = _scopeNameCache[addr]; + if (bytes(scopeName).length == 0) { + scopeName = IPatchworkScoped(addr).getScopeName(); + _scopeNameCache[addr] = scopeName; + } + } + + /** + @notice Memoized view-only wrapper for IPatchworkScoped.getScopeName() + @dev required to get optimized result from view-only functions, does not memoize result if not already memoized + @param addr Address to check + @return scopeName return value of IPatchworkScoped(addr).getScopeName() + */ + function _getScopeNameViewOnly(address addr) private view returns (string memory scopeName) { + scopeName = _scopeNameCache[addr]; + if (bytes(scopeName).length == 0) { + scopeName = IPatchworkScoped(addr).getScopeName(); + } + } + modifier onlyProtoOwnerBanker() { if (msg.sender != owner() && _protocolBankers[msg.sender] == false) { revert NotAuthorized(msg.sender); diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index 10d5e0c..a1b71fe 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -271,11 +271,10 @@ contract PatchworkProtocolTest is Test { _prot.setScopeRules(_scopeName, false, false, false); _prot.assign(address(_testFragmentLiteRefNFT), fragmentTokenId1, address(_testFragmentLiteRefNFT), fragmentTokenId2); _prot.assign(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2); + // Memoization will prevent the scope change from taking effect. _testFragmentLiteRefNFT.setScopeName("foo"); _testMultiFragmentNFT.setScopeName("foo"); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, "foo")); _prot.unassign(address(_testFragmentLiteRefNFT), fragmentTokenId1, address(_testFragmentLiteRefNFT), fragmentTokenId2); - vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeDoesNotExist.selector, "foo")); _prot.unassign(address(_testMultiFragmentNFT), multi1, address(_testFragmentLiteRefNFT), fragmentTokenId2, 0); } From c26198aaf41ab828d71842607f3810b939debf16 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Sun, 7 Jan 2024 10:08:25 -0800 Subject: [PATCH 36/63] Gas tests to prove/disprove some potential optimizations (#59) --- test/Gas.t.sol | 101 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 test/Gas.t.sol diff --git a/test/Gas.t.sol b/test/Gas.t.sol new file mode 100644 index 0000000..7a1966a --- /dev/null +++ b/test/Gas.t.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "../src/PatchworkProtocol.sol"; +import "./nfts/Test1155PatchNFT.sol"; +import "./nfts/TestBase1155.sol"; +import "./nfts/TestFragmentLiteRefNFT.sol"; +import "./nfts/TestDynamicArrayLiteRefNFT.sol"; +import "./nfts/TestMultiFragmentNFT.sol"; +import "./nfts/TestPatchLiteRefNFT.sol"; +import "./nfts/TestAccountPatchNFT.sol"; +import "./nfts/TestBaseNFT.sol"; + +contract GasTest is Test { + + PatchworkProtocol _prot; + + string _scopeName; + address _defaultUser; + address _scopeOwner; + address _patchworkOwner; + address _userAddress; + address _user2Address; + TestFragmentLiteRefNFT _lr; + mapping(bytes32 => uint8) private _supportedInterfaceCache; + mapping(address => string) private _scopeNameCache; + + function setUp() public { + _defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; + _patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; + _userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; + _user2Address = address(550001); + _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; + + vm.startPrank(_scopeOwner); + _scopeName = "testscope"; + _lr = new TestFragmentLiteRefNFT(address(_prot)); + // call for every case as control / baseline + _setupCache(); + } + + function testDirect165() public { + assertTrue(_lr.supportsInterface(type(IERC721).interfaceId)); + assertTrue(_lr.supportsInterface(type(IPatchwork721).interfaceId)); + assertTrue(_lr.supportsInterface(type(IPatchworkScoped).interfaceId)); + assertTrue(_lr.supportsInterface(type(IPatchworkSingleAssignable).interfaceId)); + } + + function testStored165() public { + assertEq(1, _supportedInterfaceCache[keccak256(abi.encodePacked(address(_lr), type(IERC721).interfaceId))]); + assertEq(1, _supportedInterfaceCache[keccak256(abi.encodePacked(address(_lr), type(IPatchwork721).interfaceId))]); + assertEq(1, _supportedInterfaceCache[keccak256(abi.encodePacked(address(_lr), type(IPatchworkScoped).interfaceId))]); + assertEq(1, _supportedInterfaceCache[keccak256(abi.encodePacked(address(_lr), type(IPatchworkSingleAssignable).interfaceId))]); + } + + function testDirectScopeName1() public { + // control for cold access + assertEq(_scopeName, _lr.getScopeName()); + } + + function testDirectScopeName4() public { + // control for warm access + assertEq(_scopeName, _lr.getScopeName()); + assertEq(_scopeName, _lr.getScopeName()); + assertEq(_scopeName, _lr.getScopeName()); + assertEq(_scopeName, _lr.getScopeName()); + } + + function testStoredScopeName1() public { + // cold access + assertEq(_scopeName, _scopeNameCache[address(_lr)]); + } + + function testStoredScopeName4() public { + // warm access + assertEq(_scopeName, _scopeNameCache[address(_lr)]); + assertEq(_scopeName, _scopeNameCache[address(_lr)]); + assertEq(_scopeName, _scopeNameCache[address(_lr)]); + assertEq(_scopeName, _scopeNameCache[address(_lr)]); + } + + function _setupCache() private { + if (_lr.supportsInterface(type(IERC721).interfaceId)) { + _supportedInterfaceCache[keccak256(abi.encodePacked(address(_lr), type(IERC721).interfaceId))] = 1; + } + if (_lr.supportsInterface(type(IPatchwork721).interfaceId)) { + _supportedInterfaceCache[keccak256(abi.encodePacked(address(_lr), type(IPatchwork721).interfaceId))] = 1; + } + if (_lr.supportsInterface(type(IPatchworkScoped).interfaceId)) { + _supportedInterfaceCache[keccak256(abi.encodePacked(address(_lr), type(IPatchworkScoped).interfaceId))] = 1; + } + if (_lr.supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { + _supportedInterfaceCache[keccak256(abi.encodePacked(address(_lr), type(IPatchworkSingleAssignable).interfaceId))] = 1; + } + _scopeNameCache[address(_lr)] = _lr.getScopeName(); + } + +} \ No newline at end of file From 485e076922cbb41d724c8af32d4b9ae3f05dbe54 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Mon, 8 Jan 2024 10:18:57 -0800 Subject: [PATCH 37/63] Revert "ERC165 optimizations (#57)" (#60) This reverts commit c4880f652d5923d364eb75ccab3cd61918cd47cb. --- src/IPatchworkProtocol.sol | 7 -- src/PatchworkProtocol.sol | 124 +++++++++++++++++------------------ test/Fees.t.sol | 22 +++---- test/PatchworkProtocol.t.sol | 3 +- 4 files changed, 72 insertions(+), 84 deletions(-) diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index aca4c88..50bc757 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -933,11 +933,4 @@ interface IPatchworkProtocol { @param tokenId The ID of the token whose ownership tree needs to be updated */ function updateOwnershipTree(address addr, uint256 tokenId) external; - - /** - @notice Clear supported interface memoization for the msg.sender - @dev use this if you use an upgradeable contract that may add or remove ERC165 supportsInterface signatures - @param sig The interfaceId of the type to clear - */ - function clearSupportedInterface(bytes4 sig) external; } \ No newline at end of file diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index a5f8810..00090b9 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -42,32 +42,29 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @notice unique references @dev A hash of target + targetTokenId + literef provides uniqueness */ - mapping(bytes32 => bool) private _liteRefs; + mapping(bytes32 => bool) _liteRefs; /** @notice unique patches @dev Hash of the patch mapped to a boolean indicating its uniqueness */ - mapping(bytes32 => bool) private _uniquePatches; + mapping(bytes32 => bool) _uniquePatches; /// Balance of the protocol - uint256 private _protocolBalance; + uint256 _protocolBalance; /** @notice protocol bankers @dev Map of addresses authorized to set fees and withdraw funds for the protocol @dev Does not allow for scope balance withdrawl */ - mapping(address => bool) private _protocolBankers; + mapping(address => bool) _protocolBankers; /// Current protocol fee configuration - ProtocolFeeConfig private _protocolFeeConfig; + ProtocolFeeConfig _protocolFeeConfig; /// scope-based fee overrides - mapping(string => ProtocolFeeOverride) private _scopeFeeOverrides; - - /// Supported interface cache - mapping(bytes32 => uint8) private _supportedInterfaceCache; + mapping(string => ProtocolFeeOverride) _scopeFeeOverrides; /// Scope name cache mapping(address => string) private _scopeNameCache; @@ -179,6 +176,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-setMintConfiguration} */ function setMintConfiguration(address addr, MintConfig memory config) public { + if (!IERC165(addr).supportsInterface(type(IPatchworkMintable).interfaceId)) { + revert UnsupportedContract(); + } string memory scopeName = _getScopeName(addr); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, addr); @@ -191,6 +191,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-getMintConfiguration} */ function getMintConfiguration(address addr) public view returns (MintConfig memory config) { + if (!IERC165(addr).supportsInterface(type(IPatchworkMintable).interfaceId)) { + revert UnsupportedContract(); + } Scope storage scope = _mustHaveScope(_getScopeNameViewOnly(addr)); return scope.mintConfigurations[addr]; } @@ -199,6 +202,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-setPatchFee} */ function setPatchFee(address addr, uint256 baseFee) public { + if (!IERC165(addr).supportsInterface(type(IPatchworkScoped).interfaceId)) { + revert UnsupportedContract(); + } string memory scopeName = _getScopeName(addr); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, addr); @@ -210,6 +216,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-getPatchFee} */ function getPatchFee(address addr) public view returns (uint256 baseFee) { + if (!IERC165(addr).supportsInterface(type(IPatchworkScoped).interfaceId)) { + revert UnsupportedContract(); + } Scope storage scope = _mustHaveScope(_getScopeNameViewOnly(addr)); return scope.patchFees[addr]; } @@ -218,6 +227,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-setAssignFee} */ function setAssignFee(address fragmentAddress, uint256 baseFee) public { + if (!IERC165(fragmentAddress).supportsInterface(type(IPatchworkScoped).interfaceId)) { + revert UnsupportedContract(); + } string memory scopeName = _getScopeName(fragmentAddress); Scope storage scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, fragmentAddress); @@ -229,6 +241,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-getAssignFee} */ function getAssignFee(address fragmentAddress) public view returns (uint256 baseFee) { + if (!IERC165(fragmentAddress).supportsInterface(type(IPatchworkScoped).interfaceId)) { + revert UnsupportedContract(); + } Scope storage scope = _mustHaveScope(_getScopeNameViewOnly(fragmentAddress)); return scope.assignFees[fragmentAddress]; } @@ -312,6 +327,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { /// Common to mints function _setupMint(address mintable) internal view returns (MintConfig memory config, string memory scopeName, Scope storage scope) { + if (!IERC165(mintable).supportsInterface(type(IPatchworkMintable).interfaceId)) { + revert UnsupportedContract(); + } scopeName = _getScopeNameViewOnly(mintable); scope = _mustHaveScope(scopeName); _mustBeWhitelisted(scopeName, scope, mintable); @@ -430,6 +448,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-patch} */ function patch(address owner, address originalAddress, uint originalTokenId, address patchAddress) external payable returns (uint256 tokenId) { + if (!IERC165(patchAddress).supportsInterface(type(IPatchworkPatch).interfaceId)) { + revert UnsupportedContract(); + } IPatchworkPatch patch_ = IPatchworkPatch(patchAddress); string memory scopeName = _getScopeName(patchAddress); Scope storage scope = _mustHaveScope(scopeName); @@ -465,6 +486,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-patch1155} */ function patch1155(address to, address originalAddress, uint originalTokenId, address originalAccount, address patchAddress) external payable returns (uint256 tokenId) { + if (!IERC165(patchAddress).supportsInterface(type(IPatchwork1155Patch).interfaceId)) { + revert UnsupportedContract(); + } IPatchwork1155Patch patch_ = IPatchwork1155Patch(patchAddress); string memory scopeName = _getScopeName(patchAddress); Scope storage scope = _mustHaveScope(scopeName); @@ -500,6 +524,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-patchAccount} */ function patchAccount(address owner, address originalAddress, address patchAddress) external payable returns (uint256 tokenId) { + if (!IERC165(patchAddress).supportsInterface(type(IPatchworkAccountPatch).interfaceId)) { + revert UnsupportedContract(); + } IPatchworkAccountPatch patch_ = IPatchworkAccountPatch(patchAddress); string memory scopeName = _getScopeName(patchAddress); Scope storage scope = _mustHaveScope(scopeName); @@ -706,13 +733,13 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev Common function to handle unassignments. */ function _unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool isDirect, uint256 targetMetadataId) private { - if (_supportsInterface(fragment, type(IPatchworkMultiAssignable).interfaceId)) { + if (IERC165(fragment).supportsInterface(type(IPatchworkMultiAssignable).interfaceId)) { if (isDirect) { unassignMulti(fragment, fragmentTokenId, target, targetTokenId, targetMetadataId); } else { unassignMulti(fragment, fragmentTokenId, target, targetTokenId); } - } else if (_supportsInterface(fragment, type(IPatchworkSingleAssignable).interfaceId)) { + } else if (IERC165(fragment).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { (address _target, uint256 _targetTokenId) = IPatchworkSingleAssignable(fragment).getAssignedTo(fragmentTokenId); if (target != _target || _targetTokenId != targetTokenId) { revert FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); @@ -825,22 +852,22 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { */ function applyTransfer(address from, address to, uint256 tokenId) public { address nft = msg.sender; - if (_supportsInterface(nft, type(IPatchworkSingleAssignable).interfaceId)) { + if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { IPatchworkSingleAssignable assignable = IPatchworkSingleAssignable(nft); (address addr,) = assignable.getAssignedTo(tokenId); if (addr != address(0)) { revert TransferBlockedByAssignment(nft, tokenId); } } - if (_supportsInterface(nft, type(IPatchworkPatch).interfaceId)) { + if (IERC165(nft).supportsInterface(type(IPatchworkPatch).interfaceId)) { revert TransferNotAllowed(nft, tokenId); } - if (_supportsInterface(nft, type(IPatchwork721).interfaceId)) { + if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { if (IPatchwork721(nft).locked(tokenId)) { revert Locked(nft, tokenId); } } - if (_supportsInterface(nft, type(IPatchworkLiteRef).interfaceId)) { + if (IERC165(nft).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { (address[] memory addresses, uint256[] memory tokenIds) = IPatchworkLiteRef(nft).loadAllStaticReferences(tokenId); for (uint i = 0; i < addresses.length; i++) { if (addresses[i] != address(0)) { @@ -851,17 +878,16 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } function _applyAssignedTransfer(address nft, address from, address to, uint256 tokenId, address assignedTo_, uint256 assignedToTokenId_) private { - if (_supportsInterface(nft, type(IPatchworkSingleAssignable).interfaceId)) { - IPatchworkSingleAssignable singleAssignable = IPatchworkSingleAssignable(nft); - (address assignedTo, uint256 assignedToTokenId) = singleAssignable.getAssignedTo(tokenId); - // 2-way Check the assignment to prevent spoofing - if (assignedTo_ != assignedTo || assignedToTokenId_ != assignedToTokenId) { - revert DataIntegrityError(assignedTo_, assignedToTokenId_, assignedTo, assignedToTokenId); - } - singleAssignable.onAssignedTransfer(from, to, tokenId); + if (!IERC165(nft).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { + revert NotPatchworkAssignable(nft); } - - if (_supportsInterface(nft, type(IPatchworkLiteRef).interfaceId)) { + (address assignedTo, uint256 assignedToTokenId) = IPatchworkSingleAssignable(nft).getAssignedTo(tokenId); + // 2-way Check the assignment to prevent spoofing + if (assignedTo_ != assignedTo || assignedToTokenId_ != assignedToTokenId) { + revert DataIntegrityError(assignedTo_, assignedToTokenId_, assignedTo, assignedToTokenId); + } + IPatchworkSingleAssignable(nft).onAssignedTransfer(from, to, tokenId); + if (IERC165(nft).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { address nft_ = nft; // local variable prevents optimizer stack issue in v0.8.18 (address[] memory addresses, uint256[] memory tokenIds) = IPatchworkLiteRef(nft).loadAllStaticReferences(tokenId); for (uint i = 0; i < addresses.length; i++) { @@ -876,7 +902,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-updateOwnershipTree} */ function updateOwnershipTree(address addr, uint256 tokenId) public { - if (_supportsInterface(addr, type(IPatchworkLiteRef).interfaceId)) { + if (IERC165(addr).supportsInterface(type(IPatchworkLiteRef).interfaceId)) { (address[] memory addresses, uint256[] memory tokenIds) = IPatchworkLiteRef(addr).loadAllStaticReferences(tokenId); for (uint i = 0; i < addresses.length; i++) { if (addresses[i] != address(0)) { @@ -884,9 +910,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } } } - if (_supportsInterface(addr, type(IPatchworkSingleAssignable).interfaceId)) { + if (IERC165(addr).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { IPatchworkSingleAssignable(addr).updateOwnership(tokenId); - } else if (_supportsInterface(addr, type(IPatchworkPatch).interfaceId)) { + } else if (IERC165(addr).supportsInterface(type(IPatchworkPatch).interfaceId)) { IPatchworkPatch(addr).updateOwnership(tokenId); } } @@ -957,12 +983,12 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @param tokenId the tokenId of nft @return frozen if the nft or an owner up the tree is frozen */ - function _isFrozen(address nft, uint256 tokenId) private returns (bool frozen) { - if (_supportsInterface(nft, type(IPatchwork721).interfaceId)) { + function _isFrozen(address nft, uint256 tokenId) private view returns (bool frozen) { + if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { if (IPatchwork721(nft).frozen(tokenId)) { return true; } - if (_supportsInterface(nft, type(IPatchworkSingleAssignable).interfaceId)) { + if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { (address assignedAddr, uint256 assignedTokenId) = IPatchworkSingleAssignable(nft).getAssignedTo(tokenId); if (assignedAddr != address(0)) { return _isFrozen(assignedAddr, assignedTokenId); @@ -978,8 +1004,8 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @param tokenId the tokenId of nft @return locked if the nft is locked */ - function _isLocked(address nft, uint256 tokenId) private returns (bool locked) { - if (_supportsInterface(nft, type(IPatchwork721).interfaceId)) { + function _isLocked(address nft, uint256 tokenId) private view returns (bool locked) { + if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { if (IPatchwork721(nft).locked(tokenId)) { return true; } @@ -987,38 +1013,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { return false; } - /** - @notice Memoizing wrapper for IERC165 supportsInterface() - @dev use clearSupportedInterface(sig) from the addr to clear if supported interfaces changes - @param addr Address to check - @param sig Signature to check at address - @return ret return value of IERC165(addr).supportsInterface(sig) - */ - function _supportsInterface(address addr, bytes4 sig) private returns (bool ret) { - bytes32 _hash = keccak256(abi.encodePacked(addr, sig)); - uint8 support = _supportedInterfaceCache[_hash]; - if (support == 1) { - return true; - } else if (support == 2) { - return false; - } else { - ret = IERC165(addr).supportsInterface(sig); - if (ret) { - _supportedInterfaceCache[_hash] = 1; - } else { - _supportedInterfaceCache[_hash] = 2; - } - return ret; - } - } - - /** - @dev See {IPatchworkProtocol-clearSupportedInterface} - */ - function clearSupportedInterface(bytes4 sig) external { - delete _supportedInterfaceCache[keccak256(abi.encodePacked(msg.sender, sig))]; - } - /** @notice Memoizing wrapper for IPatchworkScoped.getScopeName() @param addr Address to check diff --git a/test/Fees.t.sol b/test/Fees.t.sol index f1fd940..6d45a75 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -147,21 +147,21 @@ contract FeesTest is Test { function testUnsupportedContracts() public { vm.startPrank(_scopeOwner); TestBaseNFT tBase = new TestBaseNFT(); - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); _prot.setMintConfiguration(address(tBase), IPatchworkProtocol.MintConfig(1000000000, true)); - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); _prot.getMintConfiguration(address(tBase)); - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); _prot.setPatchFee(address(tBase), 1); - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); _prot.getPatchFee(address(tBase)); - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); _prot.setAssignFee(address(tBase), 1); - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); _prot.getAssignFee(address(tBase)); - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); _prot.mint(_userAddress, address(tBase), ""); - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); _prot.mintBatch(_userAddress, address(tBase), "", 5); } @@ -269,11 +269,11 @@ contract FeesTest is Test { // patch wrong types vm.startPrank(_scopeOwner); - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); _prot.patch(_userAddress, address(tBase), 2, address(t1155)); - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); _prot.patch1155(_userAddress, address(tBase1155), 3, address(0), address(t721)); - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.UnsupportedContract.selector)); _prot.patchAccount(_userAddress, _user2Address, address(t721)); } diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index a1b71fe..7a90ae5 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -874,8 +874,9 @@ contract PatchworkProtocolTest is Test { _testFragmentLiteRefNFT.registerReferenceAddress(address(_testBaseNFT)); (uint64 ref, ) = _testFragmentLiteRefNFT.getLiteReference(address(_testBaseNFT), _testBaseNFTTokenId); _testFragmentLiteRefNFT.addReference(fragment1, ref); - // Will not revert, will just do nothing + // Should revert with data integrity error vm.stopPrank(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotPatchworkAssignable.selector, address(_testBaseNFT))); vm.prank(_userAddress); _testFragmentLiteRefNFT.transferFrom(_userAddress, _user2Address, fragment1); } From 072fd68640accfb1dfc7790a73807605355badfb Mon Sep 17 00:00:00 2001 From: Rob Green Date: Mon, 8 Jan 2024 12:42:24 -0800 Subject: [PATCH 38/63] private modifiers (#61) --- src/PatchworkProtocol.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 00090b9..f9014ba 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -42,29 +42,29 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @notice unique references @dev A hash of target + targetTokenId + literef provides uniqueness */ - mapping(bytes32 => bool) _liteRefs; + mapping(bytes32 => bool) private _liteRefs; /** @notice unique patches @dev Hash of the patch mapped to a boolean indicating its uniqueness */ - mapping(bytes32 => bool) _uniquePatches; + mapping(bytes32 => bool) private _uniquePatches; /// Balance of the protocol - uint256 _protocolBalance; + uint256 private _protocolBalance; /** @notice protocol bankers @dev Map of addresses authorized to set fees and withdraw funds for the protocol @dev Does not allow for scope balance withdrawl */ - mapping(address => bool) _protocolBankers; + mapping(address => bool) private _protocolBankers; /// Current protocol fee configuration - ProtocolFeeConfig _protocolFeeConfig; + ProtocolFeeConfig private _protocolFeeConfig; /// scope-based fee overrides - mapping(string => ProtocolFeeOverride) _scopeFeeOverrides; + mapping(string => ProtocolFeeOverride) private _scopeFeeOverrides; /// Scope name cache mapping(address => string) private _scopeNameCache; From 2e2215551cb446d002bb97939dddf6270bbbc8e8 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Wed, 10 Jan 2024 15:00:49 -0800 Subject: [PATCH 39/63] Fee change timelock (#62) * wip * events * Timelock working for protocol fees * May not claim empty scope * Timelock fee changes applied to fee overrides * update #1 * Feedback changes --- src/IPatchworkProtocol.sol | 76 ++++++++++++++++++++++++++----- src/PatchworkProtocol.sol | 76 +++++++++++++++++++++++++------ test/Fees.t.sol | 86 +++++++++++++++++++++++++++++++----- test/PatchworkProtocol.t.sol | 3 ++ 4 files changed, 206 insertions(+), 35 deletions(-) diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index 50bc757..bdebf82 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -239,25 +239,44 @@ interface IPatchworkProtocol { */ error UnsupportedOperation(); + /** + @notice No proposed fee is set + */ + error NoProposedFeeSet(); + + /** + @notice Timelock has not elapsed + */ + error TimelockNotElapsed(); + /** - @notice Protocol Fee Configuration + @notice Fee Configuration */ - struct ProtocolFeeConfig { + struct FeeConfig { uint256 mintBp; /// mint basis points (10000 = 100%) uint256 patchBp; /// patch basis points (10000 = 100%) uint256 assignBp; /// assign basis points (10000 = 100%) } /** - @notice Protocol Fee Override + @notice Fee Configuration Override */ - struct ProtocolFeeOverride { + struct FeeConfigOverride { uint256 mintBp; /// mint basis points (10000 = 100%) uint256 patchBp; /// patch basis points (10000 = 100%) uint256 assignBp; /// assign basis points (10000 = 100%) bool active; /// true for present } + /** + @notice Proposal to change a fee configuration for either protocol or scope override + */ + struct ProposedFeeConfig { + FeeConfig config; + uint256 timestamp; + bool active; /// Used to enable/disable overrides - ignored for protocol + } + /** @notice Mint configuration */ @@ -542,6 +561,26 @@ interface IPatchworkProtocol { */ event MintBatch(address indexed actor, string scopeName, address indexed to, address indexed mintable, bytes data, uint256 quantity); + /** + @notice Emitted on protocol fee config proposed + */ + event ProtocolFeeConfigPropose(FeeConfig config); + + /** + @notice Emitted on protocol fee config committed + */ + event ProtocolFeeConfigCommit(FeeConfig config); + + /** + @notice Emitted on scope fee config override proposed + */ + event ScopeFeeOverridePropose(string scopeName, FeeConfigOverride config); + + /** + @notice Emitted on scope fee config override committed + */ + event ScopeFeeOverrideCommit(string scopeName, FeeConfigOverride config); + /** @notice Claim a scope @param scopeName the name of the scope @@ -715,30 +754,45 @@ interface IPatchworkProtocol { function mintBatch(address to, address mintable, bytes calldata data, uint256 quantity) external payable returns (uint256[] memory tokenIds); /** - @notice Set the protocol fee configuration + @notice Proposes a protocol fee configuration @dev must be protocol owner or banker to call + @dev configuration does not apply until commitProtocolFeeConfig is called @param config The protocol fee configuration to be set */ - function setProtocolFeeConfig(ProtocolFeeConfig memory config) external; + function proposeProtocolFeeConfig(FeeConfig memory config) external; + + /** + @notice Commits the current proposed protocol fee configuration + @dev must be protocol owner or banker to call + @dev may only be called after timelock has passed + */ + function commitProtocolFeeConfig() external; /** @notice Get the current protocol fee configuration @return config The current protocol fee configuration */ - function getProtocolFeeConfig() external view returns (ProtocolFeeConfig memory config); + function getProtocolFeeConfig() external view returns (FeeConfig memory config); /** - @notice Set the protocol fee override for a scope + @notice Proposes a protocol fee override for a scope @dev must be protocol owner or banker to call - @param config The protocol fee configuration to be set + @param config The protocol fee override configuration to be set + */ + function proposeScopeFeeOverride(string memory scopeName, FeeConfigOverride memory config) external; + + /** + @notice Commits the current proposed protocol fee override configuration for a scope + @dev must be protocol owner or banker to call + @dev may only be called after timelock has passed */ - function setScopeFeeOverride(string memory scopeName, ProtocolFeeOverride memory config) external; + function commitScopeFeeOverride(string memory scopeName) external; /** @notice Get the protocol fee override for a scope @return config The current protocol fee override */ - function getScopeFeeOverride(string memory scopeName) external view returns (ProtocolFeeOverride memory config); + function getScopeFeeOverride(string memory scopeName) external view returns (FeeConfigOverride memory config); /** @notice Add a banker to the protocol diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index f9014ba..1f79cb8 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -61,14 +61,20 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { mapping(address => bool) private _protocolBankers; /// Current protocol fee configuration - ProtocolFeeConfig private _protocolFeeConfig; + FeeConfig private _protocolFeeConfig; + + /// Proposed protocol fee configuration + mapping(string => ProposedFeeConfig) private _proposedFeeConfigs; /// scope-based fee overrides - mapping(string => ProtocolFeeOverride) private _scopeFeeOverrides; + mapping(string => FeeConfigOverride) private _scopeFeeOverrides; /// Scope name cache mapping(address => string) private _scopeNameCache; + /// How much time must elapse before a fee change can be committed (1209600 = 2 weeks) + uint256 public constant FEE_CHANGE_TIMELOCK = 1209600; + // TODO maybe not necessary uint256 public constant TRANSFER_GAS_LIMIT = 5000; @@ -79,6 +85,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-claimScope} */ function claimScope(string calldata scopeName) public { + if (bytes(scopeName).length == 0) { + revert NotAuthorized(msg.sender); + } Scope storage s = _scopes[scopeName]; if (s.owner != address(0)) { revert ScopeExists(scopeName); @@ -344,7 +353,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { // Account for 100% of the message value if (msg.value != 0) { uint256 mintBp; - ProtocolFeeOverride storage feeOverride = _scopeFeeOverrides[scopeName]; + FeeConfigOverride storage feeOverride = _scopeFeeOverrides[scopeName]; if (feeOverride.active) { mintBp = feeOverride.mintBp; } else { @@ -357,34 +366,75 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } /** - @dev See {IPatchworkProtocol-setProtocolFeeConfig} + @dev See {IPatchworkProtocol-proposeProtocolFeeConfig} */ - function setProtocolFeeConfig(ProtocolFeeConfig memory config) public onlyProtoOwnerBanker { + function proposeProtocolFeeConfig(FeeConfig memory config) public onlyProtoOwnerBanker { + _proposedFeeConfigs[""] = ProposedFeeConfig(config, block.timestamp, true); + emit ProtocolFeeConfigPropose(config); + } + + /** + @dev See {IPatchworkProtocol-commitProtocolFeeConfig} + */ + function commitProtocolFeeConfig() public onlyProtoOwnerBanker { + (FeeConfig memory config, /* bool active */) = _preCommitFeeChange(""); _protocolFeeConfig = config; + emit ProtocolFeeConfigCommit(_protocolFeeConfig); } /** @dev See {IPatchworkProtocol-getProtocolFeeConfig} */ - function getProtocolFeeConfig() public view returns (ProtocolFeeConfig memory config) { + function getProtocolFeeConfig() public view returns (FeeConfig memory config) { return _protocolFeeConfig; } /** - @dev See {IPatchworkProtocol-setScopeFeeOverride} + @dev See {IPatchworkProtocol-proposeScopeFeeOverride} */ - function setScopeFeeOverride(string memory scopeName, ProtocolFeeOverride memory config) public onlyProtoOwnerBanker { - if (!config.active) { + function proposeScopeFeeOverride(string memory scopeName, FeeConfigOverride memory config) public onlyProtoOwnerBanker { + _proposedFeeConfigs[scopeName] = ProposedFeeConfig( + FeeConfig(config.mintBp, config.patchBp, config.assignBp), block.timestamp, config.active); + emit ScopeFeeOverridePropose(scopeName, config); + } + + /** + @dev See {IPatchworkProtocol-commitScopeFeeOverride} + */ + function commitScopeFeeOverride(string memory scopeName) public onlyProtoOwnerBanker { + (FeeConfig memory config, bool active) = _preCommitFeeChange(scopeName); + FeeConfigOverride memory feeOverride = FeeConfigOverride(config.mintBp, config.patchBp, config.assignBp, active); + if (!active) { delete _scopeFeeOverrides[scopeName]; } else { - _scopeFeeOverrides[scopeName] = config; + _scopeFeeOverrides[scopeName] = feeOverride; + } + emit ScopeFeeOverrideCommit(scopeName, feeOverride); + } + + /** + @dev commits a fee change if a proposal exists and timelock is satisfied + @param scopeName "" for protocol or the scope name + @return config The proposed config + @return active The proposed active state (only applies to fee overrides) + */ + function _preCommitFeeChange(string memory scopeName) private returns (FeeConfig memory config, bool active) { + ProposedFeeConfig storage proposal = _proposedFeeConfigs[scopeName]; + if (proposal.timestamp == 0) { + revert NoProposedFeeSet(); + } + if (block.timestamp < proposal.timestamp + FEE_CHANGE_TIMELOCK) { + revert TimelockNotElapsed(); } + config = proposal.config; + active = proposal.active; + delete _proposedFeeConfigs[scopeName]; } /** @dev See {IPatchworkProtocol-getScopeFeeOverride} */ - function getScopeFeeOverride(string memory scopeName) public view returns (ProtocolFeeOverride memory config) { + function getScopeFeeOverride(string memory scopeName) public view returns (FeeConfigOverride memory config) { return _scopeFeeOverrides[scopeName]; } @@ -566,7 +616,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } if (msg.value > 0) { uint256 patchBp; - ProtocolFeeOverride storage feeOverride = _scopeFeeOverrides[scopeName]; + FeeConfigOverride storage feeOverride = _scopeFeeOverrides[scopeName]; if (feeOverride.active) { patchBp = feeOverride.patchBp; } else { @@ -586,7 +636,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } if (msg.value > 0) { uint256 assignBp; - ProtocolFeeOverride storage feeOverride = _scopeFeeOverrides[scopeName]; + FeeConfigOverride storage feeOverride = _scopeFeeOverrides[scopeName]; if (feeOverride.active) { assignBp = feeOverride.assignBp; } else { diff --git a/test/Fees.t.sol b/test/Fees.t.sol index 6d45a75..0ed0382 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -35,7 +35,10 @@ contract FeesTest is Test { vm.prank(_patchworkOwner); _prot = new PatchworkProtocol(); vm.prank(_patchworkOwner); - _prot.setProtocolFeeConfig(IPatchworkProtocol.ProtocolFeeConfig(1000, 1000, 1000)); // 10%, 10%, 10% + _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(1000, 1000, 1000)); // 10%, 10%, 10% + skip(20000000); + vm.prank(_patchworkOwner); + _prot.commitProtocolFeeConfig(); vm.startPrank(_scopeOwner); _scopeName = "testscope"; @@ -52,15 +55,50 @@ contract FeesTest is Test { _prot.addProtocolBanker(_user2Address); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); - _prot.setProtocolFeeConfig(IPatchworkProtocol.ProtocolFeeConfig(1000, 1000, 1000)); + _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(1000, 1000, 1000)); + + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NoProposedFeeSet.selector)); + vm.prank(_patchworkOwner); + _prot.commitProtocolFeeConfig(); + + vm.prank(_patchworkOwner); + _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(150, 150, 150)); + IPatchworkProtocol.FeeConfig memory feeConfig = _prot.getProtocolFeeConfig(); + assertEq(1000, feeConfig.mintBp); + assertEq(1000, feeConfig.assignBp); + assertEq(1000, feeConfig.patchBp); + + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); + _prot.commitProtocolFeeConfig(); + + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TimelockNotElapsed.selector)); + vm.prank(_patchworkOwner); + _prot.commitProtocolFeeConfig(); + + skip(2000000); vm.prank(_patchworkOwner); - _prot.setProtocolFeeConfig(IPatchworkProtocol.ProtocolFeeConfig(150, 150, 150)); - IPatchworkProtocol.ProtocolFeeConfig memory feeConfig = _prot.getProtocolFeeConfig(); + _prot.commitProtocolFeeConfig(); + + feeConfig = _prot.getProtocolFeeConfig(); assertEq(150, feeConfig.mintBp); assertEq(150, feeConfig.assignBp); assertEq(150, feeConfig.patchBp); + + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NoProposedFeeSet.selector)); + vm.prank(_patchworkOwner); + _prot.commitProtocolFeeConfig(); + vm.prank(_user2Address); - _prot.setProtocolFeeConfig(IPatchworkProtocol.ProtocolFeeConfig(1000, 1000, 1000)); + _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(1000, 1000, 1000)); + + feeConfig = _prot.getProtocolFeeConfig(); + assertEq(150, feeConfig.mintBp); + assertEq(150, feeConfig.assignBp); + assertEq(150, feeConfig.patchBp); + + skip(2000000); + vm.prank(_patchworkOwner); + _prot.commitProtocolFeeConfig(); vm.prank(_patchworkOwner); _prot.addProtocolBanker(_defaultUser); @@ -331,21 +369,45 @@ contract FeesTest is Test { _prot.setMintConfiguration(address(fragLr), IPatchworkProtocol.MintConfig(1000000000, true)); // Scope owner cannot set fee overrides for anyone vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); - _prot.setScopeFeeOverride(_scopeName, IPatchworkProtocol.ProtocolFeeOverride(100, 100, 100, true)); // 1% + _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(100, 100, 100, true)); // 1% - IPatchworkProtocol.ProtocolFeeOverride memory protFee = _prot.getScopeFeeOverride(_scopeName); + IPatchworkProtocol.FeeConfigOverride memory protFee = _prot.getScopeFeeOverride(_scopeName); assertEq(false, protFee.active); assertEq(0, protFee.mintBp); assertEq(0, protFee.assignBp); assertEq(0, protFee.patchBp); vm.stopPrank(); + + vm.prank(_patchworkOwner); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NoProposedFeeSet.selector)); + _prot.commitScopeFeeOverride(_scopeName); + + vm.prank(_patchworkOwner); + _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(100, 100, 100, true)); // 1% + protFee = _prot.getScopeFeeOverride(_scopeName); + assertEq(false, protFee.active); + assertEq(0, protFee.mintBp); + assertEq(0, protFee.assignBp); + assertEq(0, protFee.patchBp); + vm.prank(_patchworkOwner); - _prot.setScopeFeeOverride(_scopeName, IPatchworkProtocol.ProtocolFeeOverride(100, 100, 100, true)); // 1% + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TimelockNotElapsed.selector)); + _prot.commitScopeFeeOverride(_scopeName); + skip(2000000); + vm.prank(_scopeOwner); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); + _prot.commitScopeFeeOverride(_scopeName); + vm.prank(_patchworkOwner); + _prot.commitScopeFeeOverride(_scopeName); + vm.prank(_patchworkOwner); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NoProposedFeeSet.selector)); + _prot.commitScopeFeeOverride(_scopeName); + vm.prank(_patchworkOwner); _prot.addProtocolBanker(_user2Address); vm.prank(_user2Address); - _prot.setScopeFeeOverride(_scopeName, IPatchworkProtocol.ProtocolFeeOverride(100, 100, 100, true)); // 1% + _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(100, 100, 100, true)); // 1% protFee = _prot.getScopeFeeOverride(_scopeName); assertEq(true, protFee.active); assertEq(100, protFee.mintBp); @@ -376,12 +438,14 @@ contract FeesTest is Test { vm.stopPrank(); vm.prank(_patchworkOwner); - _prot.setScopeFeeOverride(_scopeName, IPatchworkProtocol.ProtocolFeeOverride(0, 0, 0, false)); // 1% + _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(0, 0, 0, false)); // 1% + skip(2000000); + vm.prank(_patchworkOwner); + _prot.commitScopeFeeOverride(_scopeName); protFee = _prot.getScopeFeeOverride(_scopeName); assertEq(false, protFee.active); assertEq(0, protFee.mintBp); assertEq(0, protFee.assignBp); assertEq(0, protFee.patchBp); - } } \ No newline at end of file diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index 7a90ae5..180c01e 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -54,6 +54,9 @@ contract PatchworkProtocolTest is Test { } function testScopeOwnerOperator() public { + vm.startPrank(_scopeOwner); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); + _prot.claimScope(""); vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); assertEq(_prot.getScopeOwner(_scopeName), _scopeOwner); From 120299ec815afaca2186ca280a6d4a1cd6a29977 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Thu, 11 Jan 2024 11:45:31 -0800 Subject: [PATCH 40/63] Handle invalid fee configurations (#63) --- src/IPatchworkProtocol.sol | 5 +++++ src/PatchworkProtocol.sol | 15 ++++++++++++--- test/Fees.t.sol | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index bdebf82..b461ebd 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -249,6 +249,11 @@ interface IPatchworkProtocol { */ error TimelockNotElapsed(); + /** + @notice Invalid fee value + */ + error InvalidFeeValue(); + /** @notice Fee Configuration */ diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 1f79cb8..7fc0688 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -78,6 +78,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { // TODO maybe not necessary uint256 public constant TRANSFER_GAS_LIMIT = 5000; + /// The denominator for fee basis points + uint256 private constant FEE_BASIS_DENOM = 10000; + /// Constructor constructor() Ownable() ReentrancyGuard() {} @@ -359,7 +362,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { mintBp = _protocolFeeConfig.mintBp; } - uint256 protocolFee = msg.value * mintBp / 10000; + uint256 protocolFee = msg.value * mintBp / FEE_BASIS_DENOM; _protocolBalance += protocolFee; scope.balance += msg.value - protocolFee; } @@ -369,6 +372,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-proposeProtocolFeeConfig} */ function proposeProtocolFeeConfig(FeeConfig memory config) public onlyProtoOwnerBanker { + if (config.assignBp > FEE_BASIS_DENOM || config.mintBp > FEE_BASIS_DENOM || config.patchBp > FEE_BASIS_DENOM) { + revert InvalidFeeValue(); + } _proposedFeeConfigs[""] = ProposedFeeConfig(config, block.timestamp, true); emit ProtocolFeeConfigPropose(config); } @@ -393,6 +399,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-proposeScopeFeeOverride} */ function proposeScopeFeeOverride(string memory scopeName, FeeConfigOverride memory config) public onlyProtoOwnerBanker { + if (config.assignBp > FEE_BASIS_DENOM || config.mintBp > FEE_BASIS_DENOM || config.patchBp > FEE_BASIS_DENOM) { + revert InvalidFeeValue(); + } _proposedFeeConfigs[scopeName] = ProposedFeeConfig( FeeConfig(config.mintBp, config.patchBp, config.assignBp), block.timestamp, config.active); emit ScopeFeeOverridePropose(scopeName, config); @@ -622,7 +631,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { patchBp = _protocolFeeConfig.patchBp; } - uint256 protocolFee = msg.value * patchBp / 10000; + uint256 protocolFee = msg.value * patchBp / FEE_BASIS_DENOM; _protocolBalance += protocolFee; scope.balance += msg.value - protocolFee; } @@ -642,7 +651,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { assignBp = _protocolFeeConfig.assignBp; } - uint256 protocolFee = msg.value * assignBp / 10000; + uint256 protocolFee = msg.value * assignBp / FEE_BASIS_DENOM; _protocolBalance += protocolFee; scope.balance += msg.value - protocolFee; } diff --git a/test/Fees.t.sol b/test/Fees.t.sol index 0ed0382..baf0c07 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -448,4 +448,20 @@ contract FeesTest is Test { assertEq(0, protFee.assignBp); assertEq(0, protFee.patchBp); } + + function testInvalidFeeValues() public { + vm.startPrank(_patchworkOwner); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); + _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(10001, 1000, 1000)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); + _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(1000, 10001, 1000)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); + _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(1000, 1000, 10001)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); + _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(10001, 0, 0, true)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); + _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(0, 10001, 0, true)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); + _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(0, 0, 10001, true)); + } } \ No newline at end of file From 08ea99e085c24cc70747d473722516bb15c6b5c1 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Thu, 11 Jan 2024 11:45:38 -0800 Subject: [PATCH 41/63] Add assignment check to unassign (#64) --- src/PatchworkFragmentSingle.sol | 3 +++ test/PatchworkFragmentSingle.t.sol | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/PatchworkFragmentSingle.sol b/src/PatchworkFragmentSingle.sol index ef0a296..7076679 100644 --- a/src/PatchworkFragmentSingle.sol +++ b/src/PatchworkFragmentSingle.sol @@ -53,6 +53,9 @@ abstract contract PatchworkFragmentSingle is Patchwork721, IPatchworkSingleAssig @dev See {IPatchworkAssignableNFT-unassign} */ function unassign(uint256 tokenId) public virtual mustHaveTokenWriteAuth(tokenId) { + if (_assignments[tokenId].tokenAddr == address(0)) { + revert IPatchworkProtocol.FragmentNotAssigned(address(this), tokenId); + } updateOwnership(tokenId); delete _assignments[tokenId]; emit Unlocked(tokenId); diff --git a/test/PatchworkFragmentSingle.t.sol b/test/PatchworkFragmentSingle.t.sol index 7573f60..799d524 100644 --- a/test/PatchworkFragmentSingle.t.sol +++ b/test/PatchworkFragmentSingle.t.sol @@ -57,6 +57,12 @@ contract PatchworkFragmentSingleTest is Test { _testFragmentLiteRefNFT.onAssignedTransfer(address(0), address(1), 1); } + function testNotAssignedUnassign() public { + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentNotAssigned.selector, address(_testFragmentLiteRefNFT), 5)); + vm.prank(_scopeOwner); + _testFragmentLiteRefNFT.unassign(5); + } + function testLiteref56bitlimit() public { vm.prank(_scopeOwner); uint8 r1 = _testFragmentLiteRefNFT.registerReferenceAddress(address(1)); From 7cc50682afa438414eca6dacdd0a320d32f27093 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Thu, 11 Jan 2024 11:45:46 -0800 Subject: [PATCH 42/63] Added fee change events (#65) --- src/IPatchworkProtocol.sol | 10 ++++++++++ src/PatchworkProtocol.sol | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/IPatchworkProtocol.sol b/src/IPatchworkProtocol.sol index b461ebd..a9ed2d4 100644 --- a/src/IPatchworkProtocol.sol +++ b/src/IPatchworkProtocol.sol @@ -586,6 +586,16 @@ interface IPatchworkProtocol { */ event ScopeFeeOverrideCommit(string scopeName, FeeConfigOverride config); + /** + @notice Emitted on patch fee change + */ + event PatchFeeChange(string scopeName, address indexed addr, uint256 fee); + + /** + @notice Emitted on assign fee change + */ + event AssignFeeChange(string scopeName, address indexed addr, uint256 fee); + /** @notice Claim a scope @param scopeName the name of the scope diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 7fc0688..2050aa8 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -222,6 +222,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { _mustBeWhitelisted(scopeName, scope, addr); _mustBeOwnerOrOperator(scope); scope.patchFees[addr] = baseFee; + emit PatchFeeChange(scopeName, addr, baseFee); } /** @@ -247,6 +248,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { _mustBeWhitelisted(scopeName, scope, fragmentAddress); _mustBeOwnerOrOperator(scope); scope.assignFees[fragmentAddress] = baseFee; + emit AssignFeeChange(scopeName, fragmentAddress, baseFee); } /** From e1f83c37a00b2937247b05af43276c4ec4335a1d Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 12 Jan 2024 09:25:47 -0800 Subject: [PATCH 43/63] Refactored into reversible account patch subclass (#66) --- src/IPatchworkAccountPatch.sol | 7 +++++ src/PatchworkAccountPatch.sol | 51 ++++++++++++++++++++++++------- test/Fees.t.sol | 2 +- test/PatchworkAccountPatch.t.sol | 16 +++++----- test/nfts/TestAccountPatchNFT.sol | 8 ++--- 5 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/IPatchworkAccountPatch.sol b/src/IPatchworkAccountPatch.sol index 99a5984..cfd53b5 100644 --- a/src/IPatchworkAccountPatch.sol +++ b/src/IPatchworkAccountPatch.sol @@ -16,7 +16,14 @@ interface IPatchworkAccountPatch is IPatchworkScoped { @return tokenId ID of the newly minted token */ function mintPatch(address owner, address originalAccountAddress) external returns (uint256 tokenId); +} +/** +@title Patchwork Protocol Reversible Account Patch Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting Patchwork account patch standard with reverse lookup +*/ +interface IPatchworkReversibleAccountPatch is IPatchworkAccountPatch { /** @notice Returns the token ID (if it exists) for an NFT that may have been patched @dev Requires reverse storage enabled diff --git a/src/PatchworkAccountPatch.sol b/src/PatchworkAccountPatch.sol index 04f47b6..2834c27 100644 --- a/src/PatchworkAccountPatch.sol +++ b/src/PatchworkAccountPatch.sol @@ -15,9 +15,6 @@ abstract contract PatchworkAccountPatch is Patchwork721, IPatchworkAccountPatch /// @dev Mapping from token ID to the address of the NFT that this patch is applied to. mapping(uint256 => address) internal _patchedAddresses; - /// @dev Mapping of original address to token Ids for reverse lookups - mapping(address => uint256) internal _patchedAddressesRev; - /** @dev See {IERC165-supportsInterface} */ @@ -37,14 +34,38 @@ abstract contract PatchworkAccountPatch is Patchwork721, IPatchworkAccountPatch @notice stores a patch @param tokenId the tokenId of the patch @param originalAccountAddress the account we are patching - @param withReverse store reverse lookup */ - function _storePatch(uint256 tokenId, address originalAccountAddress, bool withReverse) internal virtual { + function _storePatch(uint256 tokenId, address originalAccountAddress) internal virtual { // PatchworkProtocol handles uniqueness assertion _patchedAddresses[tokenId] = originalAccountAddress; - if (withReverse) { - _patchedAddressesRev[originalAccountAddress] = tokenId; - } + } + + /** + @dev See {ERC721-_burn} + */ + function _burn(uint256 tokenId) internal virtual override { + address originalAddress = _patchedAddresses[tokenId]; + IPatchworkProtocol(_manager).patchBurnedAccount(originalAddress, address(this)); + delete _patchedAddresses[tokenId]; + super._burn(tokenId); + } + +} + +/** +@title PatchworkReversibleAccountPatch +@dev PatchworkAccountPatch with reverse lookup function +*/ +abstract contract PatchworkReversibleAccountPatch is PatchworkAccountPatch, IPatchworkReversibleAccountPatch { + /// @dev Mapping of original address to token Ids for reverse lookups + mapping(address => uint256) internal _patchedAddressesRev; + + /** + @dev See {IERC165-supportsInterface} + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(IPatchworkReversibleAccountPatch).interfaceId || + super.supportsInterface(interfaceID); } /** @@ -54,15 +75,23 @@ abstract contract PatchworkAccountPatch is Patchwork721, IPatchworkAccountPatch return _patchedAddressesRev[originalAddress]; } + /** + @notice stores a patch + @param tokenId the tokenId of the patch + @param originalAccountAddress the account we are patching + */ + function _storePatch(uint256 tokenId, address originalAccountAddress) internal virtual override { + // PatchworkProtocol handles uniqueness assertion + _patchedAddresses[tokenId] = originalAccountAddress; + _patchedAddressesRev[originalAccountAddress] = tokenId; + } + /** @dev See {ERC721-_burn} */ function _burn(uint256 tokenId) internal virtual override { address originalAddress = _patchedAddresses[tokenId]; - IPatchworkProtocol(_manager).patchBurnedAccount(originalAddress, address(this)); - delete _patchedAddresses[tokenId]; delete _patchedAddressesRev[originalAddress]; super._burn(tokenId); } - } \ No newline at end of file diff --git a/test/Fees.t.sol b/test/Fees.t.sol index baf0c07..4490c49 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -270,7 +270,7 @@ contract FeesTest is Test { TestBase1155 tBase1155 = new TestBase1155(); TestPatchLiteRefNFT t721 = new TestPatchLiteRefNFT(address(_prot)); Test1155PatchNFT t1155 = new Test1155PatchNFT(address(_prot), false); - TestAccountPatchNFT tAccount = new TestAccountPatchNFT(address(_prot), false, false); + TestAccountPatchNFT tAccount = new TestAccountPatchNFT(address(_prot), false); vm.stopPrank(); // 721 diff --git a/test/PatchworkAccountPatch.t.sol b/test/PatchworkAccountPatch.t.sol index cb39ab4..cd961b9 100644 --- a/test/PatchworkAccountPatch.t.sol +++ b/test/PatchworkAccountPatch.t.sol @@ -38,25 +38,26 @@ contract PatchworkAccountPatchTest is Test { function testScopeName() public { vm.prank(_scopeOwner); - TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, false); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); assertEq(_scopeName, testAccountPatchNFT.getScopeName()); } function testSupportsInterface() public { vm.prank(_scopeOwner); - TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, false); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC165).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC721).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC4906).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC5192).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchwork721).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchworkAccountPatch).interfaceId)); + assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchworkReversibleAccountPatch).interfaceId)); } function testAccountPatchNotSameOwner() public { // Not same owner model, yes transferrable vm.prank(_scopeOwner); - TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, false); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); // User patching is off, not authorized vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _defaultUser)); _prot.patchAccount(_userAddress, _user2Address, address(testAccountPatchNFT)); @@ -80,7 +81,7 @@ contract PatchworkAccountPatchTest is Test { function testAccountPatchSameOwner() public { // Same owner model, not transferrable vm.prank(_scopeOwner); - TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), true, false); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), true); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.MintNotAllowed.selector, _userAddress)); vm.prank(_scopeOwner); _prot.patchAccount(_userAddress, _user2Address, address(testAccountPatchNFT)); @@ -96,19 +97,16 @@ contract PatchworkAccountPatchTest is Test { _prot.setScopeRules(_scopeName, true, false, false); // Not same owner model, yes transferrable vm.prank(_scopeOwner); - TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, false); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); // User patching is on _prot.patchAccount(_userAddress, _user2Address, address(testAccountPatchNFT)); } function testReverseLookups() public { vm.startPrank(_scopeOwner); - TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false, true); + TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); // User patching is on uint256 pId = _prot.patchAccount(_userAddress, _user2Address, address(testAccountPatchNFT)); assertEq(pId, testAccountPatchNFT.getTokenIdForOriginalAccount(_user2Address)); - // disabled case - TestAccountPatchNFT testAccountPatchNFT2 = new TestAccountPatchNFT(address(_prot), false, false); - assertEq(0, testAccountPatchNFT2.getTokenIdForOriginalAccount(_user2Address)); } } \ No newline at end of file diff --git a/test/nfts/TestAccountPatchNFT.sol b/test/nfts/TestAccountPatchNFT.sol index 44f593e..9330e11 100644 --- a/test/nfts/TestAccountPatchNFT.sol +++ b/test/nfts/TestAccountPatchNFT.sol @@ -3,19 +3,17 @@ pragma solidity ^0.8.13; import "../../src/PatchworkAccountPatch.sol"; -contract TestAccountPatchNFT is PatchworkAccountPatch { +contract TestAccountPatchNFT is PatchworkReversibleAccountPatch { uint256 _nextTokenId = 0; bool _sameOwnerModel; - bool _reverseEnabled; struct TestPatchworkNFTMetadata { uint256 thing; } - constructor(address manager_, bool sameOwnerModel_, bool reverseEnabled_) Patchwork721("testscope", "TestAccountPatchNFT", "TPLR", manager_) { + constructor(address manager_, bool sameOwnerModel_) Patchwork721("testscope", "TestAccountPatchNFT", "TPLR", manager_) { _sameOwnerModel = sameOwnerModel_; - _reverseEnabled = reverseEnabled_; } function schemaURI() pure external returns (string memory) { @@ -43,7 +41,7 @@ contract TestAccountPatchNFT is PatchworkAccountPatch { } uint256 tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, original, _reverseEnabled); + _storePatch(tokenId, original); _mint(to, tokenId); _metadataStorage[tokenId] = new uint256[](1); return tokenId; From bd4e8daf87a2e3f64e1a7e311aab6f8aac3c1e60 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 12 Jan 2024 09:25:54 -0800 Subject: [PATCH 44/63] Validate redact/unredact (#68) --- src/PatchworkLiteRef.sol | 6 ++++++ test/PatchworkNFTReferences.t.sol | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/src/PatchworkLiteRef.sol b/src/PatchworkLiteRef.sol index 4bf8734..5077156 100644 --- a/src/PatchworkLiteRef.sol +++ b/src/PatchworkLiteRef.sol @@ -81,6 +81,9 @@ abstract contract PatchworkLiteRef is IPatchworkLiteRef, ERC165 { @dev See {IPatchworkLiteRef-redactReferenceAddress} */ function redactReferenceAddress(uint8 id) public virtual _mustHaveWriteAuth { + if (_referenceAddresses[id] == address(0)) { + revert IPatchworkProtocol.FragmentUnregistered(address(0)); + } _redactedReferenceIds[id] = true; emit Redact(address(this), _referenceAddresses[id]); } @@ -89,6 +92,9 @@ abstract contract PatchworkLiteRef is IPatchworkLiteRef, ERC165 { @dev See {IPatchworkLiteRef-unredactReferenceAddress} */ function unredactReferenceAddress(uint8 id) public virtual _mustHaveWriteAuth { + if (_referenceAddresses[id] == address(0)) { + revert IPatchworkProtocol.FragmentUnregistered(address(0)); + } _redactedReferenceIds[id] = false; emit Unredact(address(this), _referenceAddresses[id]); } diff --git a/test/PatchworkNFTReferences.t.sol b/test/PatchworkNFTReferences.t.sol index 75fb52a..1d6b2c3 100644 --- a/test/PatchworkNFTReferences.t.sol +++ b/test/PatchworkNFTReferences.t.sol @@ -116,6 +116,14 @@ contract PatchworkNFTCombinedTest is Test { _testPatchLiteRefNFT.unredactReferenceAddress(refIdx); vm.prank(_scopeOwner); _prot.assign(address(_testFragmentLiteRefNFT), newFrag, address(_testPatchLiteRefNFT), patchTokenId); + + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(0))); + vm.prank(_scopeOwner); + _testPatchLiteRefNFT.redactReferenceAddress(100); + + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.FragmentUnregistered.selector, address(0))); + vm.prank(_scopeOwner); + _testPatchLiteRefNFT.unredactReferenceAddress(100); } function testReferenceAddressErrors() public { From da8176512e38163bf1c1ea1c520d1f7f3c0052c1 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 12 Jan 2024 09:26:08 -0800 Subject: [PATCH 45/63] Bugfix in patch burn (#69) --- src/PatchworkPatch.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index 3c74d4c..f9e11a5 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -113,6 +113,7 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { uint256 originalTokenId = _patchedTokenIds[tokenId]; IPatchworkProtocol(_manager).patchBurned(originalAddress, originalTokenId, address(this)); delete _patchedAddresses[tokenId]; + delete _patchedTokenIds[tokenId]; delete _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))]; super._burn(tokenId); } From dad08165945b6835ebd3ec5abc87089be22ebc0f Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 12 Jan 2024 09:26:22 -0800 Subject: [PATCH 46/63] Clean up getScopeName interface and test impls (#70) --- src/Patchwork1155Patch.sol | 7 ------- src/PatchworkAccountPatch.sol | 7 ------- src/PatchworkFragmentMulti.sol | 7 ------- src/PatchworkFragmentSingle.sol | 7 ------- src/PatchworkPatch.sol | 7 ------- test/nfts/TestDynamicArrayLiteRefNFT.sol | 4 ---- test/nfts/TestFragmentLiteRefNFT.sol | 4 ---- test/nfts/TestMultiFragmentNFT.sol | 4 ---- test/nfts/TestPatchFragmentNFT.sol | 4 ---- 9 files changed, 51 deletions(-) diff --git a/src/Patchwork1155Patch.sol b/src/Patchwork1155Patch.sol index 3591182..1ce67ed 100644 --- a/src/Patchwork1155Patch.sol +++ b/src/Patchwork1155Patch.sol @@ -31,13 +31,6 @@ abstract contract Patchwork1155Patch is Patchwork721, IPatchwork1155Patch { super.supportsInterface(interfaceID); } - /** - @dev See {IPatchwork721-getScopeName} - */ - function getScopeName() public view virtual override(Patchwork721, IPatchworkScoped) returns (string memory) { - return _scopeName; - } - /** @notice stores a patch @param tokenId the tokenId of the patch diff --git a/src/PatchworkAccountPatch.sol b/src/PatchworkAccountPatch.sol index 2834c27..f33db22 100644 --- a/src/PatchworkAccountPatch.sol +++ b/src/PatchworkAccountPatch.sol @@ -23,13 +23,6 @@ abstract contract PatchworkAccountPatch is Patchwork721, IPatchworkAccountPatch super.supportsInterface(interfaceID); } - /** - @dev See {IPatchwork721-getScopeName} - */ - function getScopeName() public view virtual override(Patchwork721, IPatchworkScoped) returns (string memory) { - return _scopeName; - } - /** @notice stores a patch @param tokenId the tokenId of the patch diff --git a/src/PatchworkFragmentMulti.sol b/src/PatchworkFragmentMulti.sol index a9a0563..5b97b60 100644 --- a/src/PatchworkFragmentMulti.sol +++ b/src/PatchworkFragmentMulti.sol @@ -20,13 +20,6 @@ abstract contract PatchworkFragmentMulti is Patchwork721, IPatchworkMultiAssigna /// A mapping from token IDs in this contract to their assignments. mapping(uint256 => AssignmentStorage) internal _assignmentStorage; - /** - @dev See {IPatchwork721-getScopeName} - */ - function getScopeName() public view virtual override (Patchwork721, IPatchworkScoped) returns (string memory) { - return _scopeName; - } - /** @dev See {IERC165-supportsInterface} */ diff --git a/src/PatchworkFragmentSingle.sol b/src/PatchworkFragmentSingle.sol index 7076679..4b8e4d2 100644 --- a/src/PatchworkFragmentSingle.sol +++ b/src/PatchworkFragmentSingle.sol @@ -19,13 +19,6 @@ abstract contract PatchworkFragmentSingle is Patchwork721, IPatchworkSingleAssig /// A mapping from token IDs in this contract to their assignments. mapping(uint256 => Assignment) internal _assignments; - /** - @dev See {IPatchworkScoped-getScopeName} - */ - function getScopeName() public view virtual override (Patchwork721, IPatchworkScoped) returns (string memory) { - return _scopeName; - } - /** @dev See {IERC165-supportsInterface} */ diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index f9e11a5..34882f6 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -29,13 +29,6 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { super.supportsInterface(interfaceID); } - /** - @dev See {IPatchwork721-getScopeName} - */ - function getScopeName() public view virtual override(Patchwork721, IPatchworkScoped) returns (string memory) { - return _scopeName; - } - /** @dev will return the current owner of the patched address+tokenId @dev See {IERC721-ownerOf} diff --git a/test/nfts/TestDynamicArrayLiteRefNFT.sol b/test/nfts/TestDynamicArrayLiteRefNFT.sol index 2cb6b3c..678b127 100644 --- a/test/nfts/TestDynamicArrayLiteRefNFT.sol +++ b/test/nfts/TestDynamicArrayLiteRefNFT.sol @@ -55,10 +55,6 @@ contract TestDynamicArrayLiteRefNFT is Patchwork721, PatchworkLiteRef, IPatchwor _manager = manager_; } - function getScopeName() public view override (Patchwork721, IPatchworkScoped) returns (string memory scopeName) { - return Patchwork721.getScopeName(); - } - function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { tokenId = _nextTokenId; _nextTokenId++; diff --git a/test/nfts/TestFragmentLiteRefNFT.sol b/test/nfts/TestFragmentLiteRefNFT.sol index fab9d80..8fa826b 100644 --- a/test/nfts/TestFragmentLiteRefNFT.sol +++ b/test/nfts/TestFragmentLiteRefNFT.sol @@ -46,10 +46,6 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef, IP interfaceID == type(IPatchworkMintable).interfaceId; } - function getScopeName() public view override (PatchworkFragmentSingle, IPatchworkScoped) returns (string memory scopeName) { - return PatchworkFragmentSingle.getScopeName(); - } - function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { tokenId = _nextTokenId; _nextTokenId++; diff --git a/test/nfts/TestMultiFragmentNFT.sol b/test/nfts/TestMultiFragmentNFT.sol index f43db32..157c447 100644 --- a/test/nfts/TestMultiFragmentNFT.sol +++ b/test/nfts/TestMultiFragmentNFT.sol @@ -20,10 +20,6 @@ contract TestMultiFragmentNFT is PatchworkFragmentMulti, IPatchworkMintable { interfaceID == type(IPatchworkMintable).interfaceId; } - function getScopeName() public view override (PatchworkFragmentMulti, IPatchworkScoped) returns (string memory scopeName) { - return PatchworkFragmentMulti.getScopeName(); - } - function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { tokenId = _nextTokenId; _nextTokenId++; diff --git a/test/nfts/TestPatchFragmentNFT.sol b/test/nfts/TestPatchFragmentNFT.sol index bf9ae10..cdb4d02 100644 --- a/test/nfts/TestPatchFragmentNFT.sol +++ b/test/nfts/TestPatchFragmentNFT.sol @@ -46,10 +46,6 @@ contract TestPatchFragmentNFT is PatchworkPatch, PatchworkFragmentSingle { _manager = manager_; } - function getScopeName() public view virtual override(PatchworkPatch, PatchworkFragmentSingle) returns (string memory) { - return _scopeName; - } - function setLocked(uint256 tokenId, bool locked_) public view virtual override(PatchworkPatch, PatchworkFragmentSingle) { return PatchworkPatch.setLocked(tokenId, locked_); } From 7a68de3491288bc33ca621d894c3f0405d93cadd Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 12 Jan 2024 09:26:56 -0800 Subject: [PATCH 47/63] use mustBeManager instead of adhoc (#71) --- test/nfts/Test1155PatchNFT.sol | 5 +---- test/nfts/TestAccountPatchNFT.sol | 5 +---- test/nfts/TestPatchFragmentNFT.sol | 5 +---- test/nfts/TestPatchNFT.sol | 5 +---- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/test/nfts/Test1155PatchNFT.sol b/test/nfts/Test1155PatchNFT.sol index abd965e..14a3877 100644 --- a/test/nfts/Test1155PatchNFT.sol +++ b/test/nfts/Test1155PatchNFT.sol @@ -30,10 +30,7 @@ contract Test1155PatchNFT is Patchwork1155Patch { return MetadataSchema(1, entries); } - function mintPatch(address to, address originalNFTAddress, uint originalNFTTokenId, address account) external returns (uint256 tokenId){ - if (msg.sender != _manager) { - revert(); - } + function mintPatch(address to, address originalNFTAddress, uint originalNFTTokenId, address account) external mustBeManager returns (uint256 tokenId){ // Just for testing tokenId = _nextTokenId; _nextTokenId++; diff --git a/test/nfts/TestAccountPatchNFT.sol b/test/nfts/TestAccountPatchNFT.sol index 9330e11..5c67104 100644 --- a/test/nfts/TestAccountPatchNFT.sol +++ b/test/nfts/TestAccountPatchNFT.sol @@ -30,10 +30,7 @@ contract TestAccountPatchNFT is PatchworkReversibleAccountPatch { return MetadataSchema(1, entries); } - function mintPatch(address to, address original) public returns (uint256) { - if (msg.sender != _manager) { - revert IPatchworkProtocol.NotAuthorized(msg.sender); - } + function mintPatch(address to, address original) public mustBeManager returns (uint256) { if (_sameOwnerModel) { if (to != original) { revert IPatchworkProtocol.MintNotAllowed(to); diff --git a/test/nfts/TestPatchFragmentNFT.sol b/test/nfts/TestPatchFragmentNFT.sol index cdb4d02..4ff3a79 100644 --- a/test/nfts/TestPatchFragmentNFT.sol +++ b/test/nfts/TestPatchFragmentNFT.sol @@ -117,10 +117,7 @@ contract TestPatchFragmentNFT is PatchworkPatch, PatchworkFragmentSingle { return unpackMetadata(_metadataStorage[_tokenId]); } - function mintPatch(address originalNFTOwner, address originalNFTAddress, uint originalNFTTokenId) external returns (uint256 tokenId){ - if (msg.sender != _manager) { - revert(); - } + function mintPatch(address originalNFTOwner, address originalNFTAddress, uint originalNFTTokenId) external mustBeManager returns (uint256 tokenId){ // Just for testing tokenId = _nextTokenId; _nextTokenId++; diff --git a/test/nfts/TestPatchNFT.sol b/test/nfts/TestPatchNFT.sol index 684d2e9..10f9335 100644 --- a/test/nfts/TestPatchNFT.sol +++ b/test/nfts/TestPatchNFT.sol @@ -83,10 +83,7 @@ contract TestPatchNFT is PatchworkPatch { return unpackMetadata(_metadataStorage[_tokenId]); } - function mintPatch(address owner, address originalNFTAddress, uint originalNFTTokenId) external returns (uint256 tokenId){ - if (msg.sender != _manager) { - revert(); - } + function mintPatch(address owner, address originalNFTAddress, uint originalNFTTokenId) external mustBeManager returns (uint256 tokenId){ // require inherited ownership if (IERC721(originalNFTAddress).ownerOf(originalNFTTokenId) != owner) { revert IPatchworkProtocol.NotAuthorized(owner); From 02303ff1673bddb2dddd32433e4389195b588586 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 12 Jan 2024 18:49:40 -0800 Subject: [PATCH 48/63] proto fee ceiling (#67) --- src/PatchworkProtocol.sol | 7 +++++-- test/Fees.t.sol | 12 ++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 2050aa8..6fcb8d4 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -81,6 +81,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { /// The denominator for fee basis points uint256 private constant FEE_BASIS_DENOM = 10000; + /// The maximum basis points patchwork can ever be configured to + uint256 private constant PROTOCOL_FEE_CEILING = 3000; + /// Constructor constructor() Ownable() ReentrancyGuard() {} @@ -374,7 +377,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-proposeProtocolFeeConfig} */ function proposeProtocolFeeConfig(FeeConfig memory config) public onlyProtoOwnerBanker { - if (config.assignBp > FEE_BASIS_DENOM || config.mintBp > FEE_BASIS_DENOM || config.patchBp > FEE_BASIS_DENOM) { + if (config.assignBp > PROTOCOL_FEE_CEILING || config.mintBp > PROTOCOL_FEE_CEILING || config.patchBp > PROTOCOL_FEE_CEILING) { revert InvalidFeeValue(); } _proposedFeeConfigs[""] = ProposedFeeConfig(config, block.timestamp, true); @@ -401,7 +404,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-proposeScopeFeeOverride} */ function proposeScopeFeeOverride(string memory scopeName, FeeConfigOverride memory config) public onlyProtoOwnerBanker { - if (config.assignBp > FEE_BASIS_DENOM || config.mintBp > FEE_BASIS_DENOM || config.patchBp > FEE_BASIS_DENOM) { + if (config.assignBp > PROTOCOL_FEE_CEILING || config.mintBp > PROTOCOL_FEE_CEILING || config.patchBp > PROTOCOL_FEE_CEILING) { revert InvalidFeeValue(); } _proposedFeeConfigs[scopeName] = ProposedFeeConfig( diff --git a/test/Fees.t.sol b/test/Fees.t.sol index 4490c49..55f0724 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -452,16 +452,16 @@ contract FeesTest is Test { function testInvalidFeeValues() public { vm.startPrank(_patchworkOwner); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); - _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(10001, 1000, 1000)); + _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(3001, 1000, 1000)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); - _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(1000, 10001, 1000)); + _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(1000, 3001, 1000)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); - _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(1000, 1000, 10001)); + _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(1000, 1000, 3001)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); - _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(10001, 0, 0, true)); + _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(3001, 0, 0, true)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); - _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(0, 10001, 0, true)); + _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(0, 3001, 0, true)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); - _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(0, 0, 10001, true)); + _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(0, 0, 3001, true)); } } \ No newline at end of file From 9a8da1d873e9c77e3ee497a63ab9efcd62ae78c7 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Sat, 13 Jan 2024 14:58:47 -0800 Subject: [PATCH 49/63] Struct cleanup (#72) * Clean up assignment struct * Making structs more consistent in the patch implementations --- src/IPatchworkAssignable.sol | 7 +++++++ src/IPatchworkMultiAssignable.sol | 5 ----- src/Patchwork1155Patch.sol | 7 ++++--- src/PatchworkFragmentSingle.sol | 6 ------ src/PatchworkPatch.sol | 25 ++++++++++++++----------- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/IPatchworkAssignable.sol b/src/IPatchworkAssignable.sol index dccddb7..a666b3e 100644 --- a/src/IPatchworkAssignable.sol +++ b/src/IPatchworkAssignable.sol @@ -9,6 +9,13 @@ import "./IPatchworkScoped.sol"; @notice Interface for contracts supporting Patchwork assignment */ interface IPatchworkAssignable is IPatchworkScoped { + + /// Represents an assignment of a token from an external NFT contract to a token in this contract. + struct Assignment { + address tokenAddr; /// The address of the external NFT contract. + uint256 tokenId; /// The ID of the token in the external NFT contract. + } + /** @notice Assigns a token to another @param ourTokenId ID of our token diff --git a/src/IPatchworkMultiAssignable.sol b/src/IPatchworkMultiAssignable.sol index adee6d8..387ba9a 100644 --- a/src/IPatchworkMultiAssignable.sol +++ b/src/IPatchworkMultiAssignable.sol @@ -10,11 +10,6 @@ import "./IPatchworkAssignable.sol"; */ interface IPatchworkMultiAssignable is IPatchworkAssignable { - struct Assignment { - address tokenAddr; /// The address of the external NFT contract. - uint256 tokenId; /// The ID of the token in the external NFT contract. - } - /** @notice Checks if this fragment is assigned to a target @param ourTokenId the tokenId of the fragment diff --git a/src/Patchwork1155Patch.sol b/src/Patchwork1155Patch.sol index 1ce67ed..1a0fb2d 100644 --- a/src/Patchwork1155Patch.sol +++ b/src/Patchwork1155Patch.sol @@ -11,10 +11,11 @@ import "./IPatchwork1155Patch.sol"; */ abstract contract Patchwork1155Patch is Patchwork721, IPatchwork1155Patch { + /// @dev A canonical path to an 1155 patched struct PatchCanonical { - address addr; - uint256 tokenId; - address account; + address addr; // The address of the 1155 + uint256 tokenId; // The tokenId of the 1155 + address account; // The account for the 1155 } /// @dev Mapping from token ID to the canonical address of the NFT that this patch is applied to. diff --git a/src/PatchworkFragmentSingle.sol b/src/PatchworkFragmentSingle.sol index 4b8e4d2..0c89149 100644 --- a/src/PatchworkFragmentSingle.sol +++ b/src/PatchworkFragmentSingle.sol @@ -9,12 +9,6 @@ import "./IPatchworkSingleAssignable.sol"; @dev base implementation of a Single-relation Fragment is IPatchworkSingleAssignable */ abstract contract PatchworkFragmentSingle is Patchwork721, IPatchworkSingleAssignable { - - /// Represents an assignment of a token from an external NFT contract to a token in this contract. - struct Assignment { - address tokenAddr; /// The address of the external NFT contract. - uint256 tokenId; /// The ID of the token in the external NFT contract. - } /// A mapping from token IDs in this contract to their assignments. mapping(uint256 => Assignment) internal _assignments; diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index 34882f6..d548eed 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -12,11 +12,14 @@ import "./IPatchworkPatch.sol"; */ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { - /// @dev Mapping from token ID to the address of the NFT that this patch is applied to. - mapping(uint256 => address) internal _patchedAddresses; + /// @dev A canonical path to an 721 patched + struct PatchCanonical { + address addr; // The address of the 721 + uint256 tokenId; // The tokenId of the 721 + } - /// @dev Mapping from token ID to the token ID of the NFT that this patch is applied to. - mapping(uint256 => uint256) internal _patchedTokenIds; + /// @dev Mapping from token ID to the canonical address and tokenId of the NFT that this patch is applied to. + mapping(uint256 => PatchCanonical) internal _patchedAddresses; /// @dev Mapping of hash of original address + token ID for reverse lookups mapping(bytes32 => uint256) internal _patchedAddressesRev; // hash of patched addr+tokenid to tokenId @@ -35,7 +38,8 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { */ function ownerOf(uint256 tokenId) public view virtual override(ERC721, IERC721) returns (address) { // Default is inherited ownership - return IERC721(_patchedAddresses[tokenId]).ownerOf(_patchedTokenIds[tokenId]); + PatchCanonical storage canonical = _patchedAddresses[tokenId]; + return IERC721(canonical.addr).ownerOf(canonical.tokenId); } /** @@ -46,8 +50,7 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { @param withReverse store reverse lookup */ function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId, bool withReverse) internal virtual { - _patchedAddresses[tokenId] = originalAddress; - _patchedTokenIds[tokenId] = originalTokenId; + _patchedAddresses[tokenId] = PatchCanonical(originalAddress, originalTokenId); if (withReverse) { _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))] = tokenId; } @@ -64,7 +67,7 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { @dev See {IPatchworkPatch-updateOwnership} */ function updateOwnership(uint256 tokenId) public virtual { - address patchedAddr = _patchedAddresses[tokenId]; + address patchedAddr = _patchedAddresses[tokenId].addr; if (patchedAddr != address(0)) { address owner_ = ownerOf(tokenId); address curOwner = super.ownerOf(tokenId); @@ -102,11 +105,11 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { @dev See {ERC721-_burn} */ function _burn(uint256 tokenId) internal virtual override { - address originalAddress = _patchedAddresses[tokenId]; - uint256 originalTokenId = _patchedTokenIds[tokenId]; + PatchCanonical storage canonical = _patchedAddresses[tokenId]; + address originalAddress = canonical.addr; + uint256 originalTokenId = canonical.tokenId; IPatchworkProtocol(_manager).patchBurned(originalAddress, originalTokenId, address(this)); delete _patchedAddresses[tokenId]; - delete _patchedTokenIds[tokenId]; delete _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))]; super._burn(tokenId); } From a5ff036336793a564a31cb57fc917a6c08631df5 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Sat, 13 Jan 2024 15:02:10 -0800 Subject: [PATCH 50/63] More reversibles (#73) * Base patch reversible * Resolving all reversibles --- src/IPatchwork1155Patch.sol | 7 ++++ src/IPatchworkPatch.sol | 7 ++++ src/Patchwork1155Patch.sol | 53 +++++++++++++++++++++----- src/PatchworkPatch.sol | 61 ++++++++++++++++++++---------- test/Fees.t.sol | 2 +- test/Patchwork1155Patch.t.sol | 28 ++++++++------ test/PatchworkPatch.t.sol | 15 ++++++-- test/nfts/Test1155PatchNFT.sol | 47 +++++++++++++++++++++-- test/nfts/TestPatchFragmentNFT.sol | 14 +++---- test/nfts/TestPatchLiteRefNFT.sol | 2 +- test/nfts/TestPatchNFT.sol | 2 +- 11 files changed, 180 insertions(+), 58 deletions(-) diff --git a/src/IPatchwork1155Patch.sol b/src/IPatchwork1155Patch.sol index 001e0ec..f49f436 100644 --- a/src/IPatchwork1155Patch.sol +++ b/src/IPatchwork1155Patch.sol @@ -18,7 +18,14 @@ interface IPatchwork1155Patch is IPatchworkScoped { @return tokenId ID of the newly minted token */ function mintPatch(address to, address originalAddress, uint256 originalTokenId, address originalAccount) external returns (uint256 tokenId); +} +/** +@title Patchwork Protocol Reversible 1155 Patch Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting Patchwork patch standard with reverse lookup +*/ +interface IPatchworkReversible1155Patch is IPatchwork1155Patch { /** @notice Returns the token ID (if it exists) for an NFT that may have been patched @dev Requires reverse storage enabled diff --git a/src/IPatchworkPatch.sol b/src/IPatchworkPatch.sol index 5632820..b420327 100644 --- a/src/IPatchworkPatch.sol +++ b/src/IPatchworkPatch.sol @@ -30,7 +30,14 @@ interface IPatchworkPatch is IPatchworkScoped { @return address Address of the owner */ function unpatchedOwnerOf(uint256 tokenId) external view returns (address); +} +/** +@title Patchwork Protocol Reversible Patch Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting Patchwork patch standard with reverse lookup +*/ +interface IPatchworkReversiblePatch is IPatchworkPatch { /** @notice Returns the token ID (if it exists) for an NFT that may have been patched @dev Requires reverse storage enabled diff --git a/src/Patchwork1155Patch.sol b/src/Patchwork1155Patch.sol index 1a0fb2d..f411d4c 100644 --- a/src/Patchwork1155Patch.sol +++ b/src/Patchwork1155Patch.sol @@ -21,9 +21,6 @@ abstract contract Patchwork1155Patch is Patchwork721, IPatchwork1155Patch { /// @dev Mapping from token ID to the canonical address of the NFT that this patch is applied to. mapping(uint256 => PatchCanonical) internal _patchedAddresses; - /// @dev Mapping of hash of original address + token ID + account for reverse lookups - mapping(bytes32 => uint256) internal _patchedAddressesRev; // hash of patched addr+tokenid+account to tokenId - /** @dev See {IERC165-supportsInterface} */ @@ -37,14 +34,40 @@ abstract contract Patchwork1155Patch is Patchwork721, IPatchwork1155Patch { @param tokenId the tokenId of the patch @param originalAddress the address of the original ERC-1155 we are patching @param originalTokenId the tokenId of the original ERC-1155 we are patching - @param withReverse store reverse lookup @param account the account of the ERC-1155 we are patching */ - function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId, address account, bool withReverse) internal virtual { + function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId, address account) internal virtual { _patchedAddresses[tokenId] = PatchCanonical(originalAddress, originalTokenId, account); - if (withReverse) { - _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId, account))] = tokenId; - } + } + + /** + @dev See {ERC721-_burn} + */ + function _burn(uint256 tokenId) internal virtual override { + PatchCanonical storage canonical = _patchedAddresses[tokenId]; + address originalAddress = canonical.addr; + uint256 originalTokenId = canonical.tokenId; + address account = canonical.account; + IPatchworkProtocol(_manager).patchBurned1155(originalAddress, originalTokenId, account, address(this)); + delete _patchedAddresses[tokenId]; + super._burn(tokenId); + } +} + +/** +@title PatchworkReversible1155Patch +@dev Patchwork1155Patch with reverse lookup function +*/ +abstract contract PatchworkReversible1155Patch is Patchwork1155Patch, IPatchworkReversible1155Patch { + /// @dev Mapping of hash of original address + token ID + account for reverse lookups + mapping(bytes32 => uint256) internal _patchedAddressesRev; // hash of patched addr+tokenid+account to tokenId + + /** + @dev See {IERC165-supportsInterface} + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(IPatchworkReversible1155Patch).interfaceId || + super.supportsInterface(interfaceID); } /** @@ -54,6 +77,18 @@ abstract contract Patchwork1155Patch is Patchwork721, IPatchwork1155Patch { return _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId, originalAccount))]; } + /** + @notice stores a patch + @param tokenId the tokenId of the patch + @param originalAddress the address of the original ERC-1155 we are patching + @param originalTokenId the tokenId of the original ERC-1155 we are patching + @param account the account of the ERC-1155 we are patching + */ + function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId, address account) internal virtual override { + _patchedAddresses[tokenId] = PatchCanonical(originalAddress, originalTokenId, account); + _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId, account))] = tokenId; + } + /** @dev See {ERC721-_burn} */ @@ -62,8 +97,6 @@ abstract contract Patchwork1155Patch is Patchwork721, IPatchwork1155Patch { address originalAddress = canonical.addr; uint256 originalTokenId = canonical.tokenId; address account = canonical.account; - IPatchworkProtocol(_manager).patchBurned1155(originalAddress, originalTokenId, account, address(this)); - delete _patchedAddresses[tokenId]; delete _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId, account))]; super._burn(tokenId); } diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index d548eed..0183f62 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -21,9 +21,6 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { /// @dev Mapping from token ID to the canonical address and tokenId of the NFT that this patch is applied to. mapping(uint256 => PatchCanonical) internal _patchedAddresses; - /// @dev Mapping of hash of original address + token ID for reverse lookups - mapping(bytes32 => uint256) internal _patchedAddressesRev; // hash of patched addr+tokenid to tokenId - /** @dev See {IERC165-supportsInterface} */ @@ -47,20 +44,9 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { @param tokenId the tokenId of the patch @param originalAddress the address of the original ERC-721 we are patching @param originalTokenId the tokenId of the original ERC-721 we are patching - @param withReverse store reverse lookup */ - function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId, bool withReverse) internal virtual { + function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId) internal virtual { _patchedAddresses[tokenId] = PatchCanonical(originalAddress, originalTokenId); - if (withReverse) { - _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))] = tokenId; - } - } - - /** - @dev See {IPatchworkPatch-getTokenIdForOriginal721} - */ - function getTokenIdForOriginal721(address originalAddress, uint256 originalTokenId) public view virtual returns (uint256 tokenId) { - return _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))]; } /** @@ -106,11 +92,48 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { */ function _burn(uint256 tokenId) internal virtual override { PatchCanonical storage canonical = _patchedAddresses[tokenId]; - address originalAddress = canonical.addr; - uint256 originalTokenId = canonical.tokenId; - IPatchworkProtocol(_manager).patchBurned(originalAddress, originalTokenId, address(this)); + IPatchworkProtocol(_manager).patchBurned(canonical.addr, canonical.tokenId, address(this)); delete _patchedAddresses[tokenId]; - delete _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))]; + super._burn(tokenId); + } +} + +abstract contract PatchworkReversiblePatch is PatchworkPatch, IPatchworkReversiblePatch { + /// @dev Mapping of hash of original address + token ID for reverse lookups + mapping(bytes32 => uint256) internal _patchedAddressesRev; // hash of patched addr+tokenid to tokenId + + /** + @dev See {IERC165-supportsInterface} + */ + function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { + return interfaceID == type(IPatchworkReversiblePatch).interfaceId || + super.supportsInterface(interfaceID); + } + + /** + @dev See {IPatchworkPatch-getTokenIdForOriginal721} + */ + function getTokenIdForOriginal721(address originalAddress, uint256 originalTokenId) public view virtual returns (uint256 tokenId) { + return _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))]; + } + + /** + @notice stores a patch + @param tokenId the tokenId of the patch + @param originalAddress the address of the original ERC-721 we are patching + @param originalTokenId the tokenId of the original ERC-721 we are patching + */ + function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId) internal virtual override { + super._storePatch(tokenId, originalAddress, originalTokenId); + _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))] = tokenId; + } + + /** + @dev See {ERC721-_burn} + */ + function _burn(uint256 tokenId) internal virtual override { + PatchCanonical storage canonical = _patchedAddresses[tokenId]; + delete _patchedAddressesRev[keccak256(abi.encodePacked(canonical.addr, canonical.tokenId))]; super._burn(tokenId); } } \ No newline at end of file diff --git a/test/Fees.t.sol b/test/Fees.t.sol index 55f0724..64605c8 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -269,7 +269,7 @@ contract FeesTest is Test { TestBaseNFT tBase = new TestBaseNFT(); TestBase1155 tBase1155 = new TestBase1155(); TestPatchLiteRefNFT t721 = new TestPatchLiteRefNFT(address(_prot)); - Test1155PatchNFT t1155 = new Test1155PatchNFT(address(_prot), false); + Test1155PatchNFT t1155 = new Test1155PatchNFT(address(_prot)); TestAccountPatchNFT tAccount = new TestAccountPatchNFT(address(_prot), false); vm.stopPrank(); diff --git a/test/Patchwork1155Patch.t.sol b/test/Patchwork1155Patch.t.sol index 6ba271d..ec89144 100644 --- a/test/Patchwork1155Patch.t.sol +++ b/test/Patchwork1155Patch.t.sol @@ -39,24 +39,34 @@ contract Patchwork1155PatchTest is Test { function testScopeName() public { vm.prank(_scopeOwner); - Test1155PatchNFT testAccountPatchNFT = new Test1155PatchNFT(address(_prot), false); + Test1155PatchNFT testAccountPatchNFT = new Test1155PatchNFT(address(_prot)); assertEq(_scopeName, testAccountPatchNFT.getScopeName()); } function testSupportsInterface() public { vm.prank(_scopeOwner); - Test1155PatchNFT testAccountPatchNFT = new Test1155PatchNFT(address(_prot), false); + Test1155PatchNFT testAccountPatchNFT = new Test1155PatchNFT(address(_prot)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC165).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC721).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC4906).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IERC5192).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchwork721).interfaceId)); assertTrue(testAccountPatchNFT.supportsInterface(type(IPatchwork1155Patch).interfaceId)); + assertFalse(testAccountPatchNFT.supportsInterface(type(IPatchworkReversible1155Patch).interfaceId)); + + TestReversible1155PatchNFT t = new TestReversible1155PatchNFT(address(_prot)); + assertTrue(t.supportsInterface(type(IERC165).interfaceId)); + assertTrue(t.supportsInterface(type(IERC721).interfaceId)); + assertTrue(t.supportsInterface(type(IERC4906).interfaceId)); + assertTrue(t.supportsInterface(type(IERC5192).interfaceId)); + assertTrue(t.supportsInterface(type(IPatchwork721).interfaceId)); + assertTrue(t.supportsInterface(type(IPatchwork1155Patch).interfaceId)); + assertTrue(t.supportsInterface(type(IPatchworkReversible1155Patch).interfaceId)); } function test1155Patch() public { vm.startPrank(_scopeOwner); - Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot), false); + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot)); TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); vm.stopPrank(); @@ -76,7 +86,7 @@ contract Patchwork1155PatchTest is Test { function test1155PatchProto() public { vm.startPrank(_scopeOwner); - Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot), false); + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot)); TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); @@ -102,7 +112,7 @@ contract Patchwork1155PatchTest is Test { vm.prank(_scopeOwner); _prot.setScopeRules(_scopeName, true, false, false); // Not same owner model, yes transferrable - Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot), false); + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot)); TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); // user can mint @@ -111,7 +121,7 @@ contract Patchwork1155PatchTest is Test { function testBurn() public { vm.startPrank(_scopeOwner); - Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot), false); + Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot)); TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); uint256 pId = _prot.patch1155(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); @@ -122,14 +132,10 @@ contract Patchwork1155PatchTest is Test { function testReverseLookups() public { vm.startPrank(_scopeOwner); - Test1155PatchNFT test1155PatchNFT = new Test1155PatchNFT(address(_prot), true); + TestReversible1155PatchNFT test1155PatchNFT = new TestReversible1155PatchNFT(address(_prot)); TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); uint256 pId = _prot.patch1155(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); assertEq(pId, test1155PatchNFT.getTokenIdForOriginal1155(address(base1155), b, _userAddress)); - // testing not enabled - Test1155PatchNFT test1155PatchNFT2 = new Test1155PatchNFT(address(_prot), false); - // 0 is default / not-enabled - assertEq(0, test1155PatchNFT2.getTokenIdForOriginal1155(address(base1155), b, _userAddress)); } } \ No newline at end of file diff --git a/test/PatchworkPatch.t.sol b/test/PatchworkPatch.t.sol index 7918306..d9c76fb 100644 --- a/test/PatchworkPatch.t.sol +++ b/test/PatchworkPatch.t.sol @@ -54,6 +54,15 @@ contract PatchworkPatchTest is Test { assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IPatchwork721).interfaceId)); assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IPatchworkLiteRef).interfaceId)); assertTrue(_testPatchLiteRefNFT.supportsInterface(type(IPatchworkPatch).interfaceId)); + assertFalse(_testPatchLiteRefNFT.supportsInterface(type(IPatchworkReversiblePatch).interfaceId)); + TestPatchFragmentNFT testPatchFragmentNFT = new TestPatchFragmentNFT(address(_prot)); + assertTrue(testPatchFragmentNFT.supportsInterface(type(IERC165).interfaceId)); + assertTrue(testPatchFragmentNFT.supportsInterface(type(IERC721).interfaceId)); + assertTrue(testPatchFragmentNFT.supportsInterface(type(IERC4906).interfaceId)); + assertTrue(testPatchFragmentNFT.supportsInterface(type(IERC5192).interfaceId)); + assertTrue(testPatchFragmentNFT.supportsInterface(type(IPatchwork721).interfaceId)); + assertTrue(testPatchFragmentNFT.supportsInterface(type(IPatchworkPatch).interfaceId)); + assertTrue(testPatchFragmentNFT.supportsInterface(type(IPatchworkReversiblePatch).interfaceId)); } function testLocks() public { @@ -93,10 +102,8 @@ contract PatchworkPatchTest is Test { uint256 liteRefId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId, address(_testPatchLiteRefNFT)); uint256 liteRefId2 = _prot.patch(_user2Address, address(_testBaseNFT), baseTokenId2, address(_testPatchLiteRefNFT)); uint256 fragmentTokenId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId3, address(testPatchFragmentNFT)); - // check reverse lookups - testPatchLiteRefNFT is disabled, always 0. patchFragmentNFT is enabled - assertEq(0, _testPatchLiteRefNFT.getTokenIdForOriginal721(address(_testBaseNFT), baseTokenId)); - assertEq(0, _testPatchLiteRefNFT.getTokenIdForOriginal721(address(_testBaseNFT), baseTokenId2)); - assertEq(fragmentTokenId, _testPatchLiteRefNFT.getTokenIdForOriginal721(address(testPatchFragmentNFT), baseTokenId3)); + // check reverse lookups + assertEq(fragmentTokenId, testPatchFragmentNFT.getTokenIdForOriginal721(address(_testBaseNFT), baseTokenId3)); // cannot assign patch to a literef that this person does not own vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); _prot.assign(address(testPatchFragmentNFT), fragmentTokenId, address(_testPatchLiteRefNFT), liteRefId2); diff --git a/test/nfts/Test1155PatchNFT.sol b/test/nfts/Test1155PatchNFT.sol index 14a3877..c465e9e 100644 --- a/test/nfts/Test1155PatchNFT.sol +++ b/test/nfts/Test1155PatchNFT.sol @@ -6,14 +6,12 @@ import "../../src/Patchwork1155Patch.sol"; contract Test1155PatchNFT is Patchwork1155Patch { uint256 _nextTokenId = 0; - bool _reverseEnabled = false; struct Test1155PatchNFTMetadata { uint256 thing; } - constructor(address manager_, bool reverseEnabled_) Patchwork721("testscope", "Test1155PatchNFT", "TPLR", manager_) { - _reverseEnabled = reverseEnabled_; + constructor(address manager_) Patchwork721("testscope", "Test1155PatchNFT", "TPLR", manager_) { } function schemaURI() pure external returns (string memory) { @@ -34,7 +32,48 @@ contract Test1155PatchNFT is Patchwork1155Patch { // Just for testing tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, account, _reverseEnabled); + _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, account); + _safeMint(to, tokenId); + _metadataStorage[tokenId] = new uint256[](1); + return tokenId; + } + + function burn(uint256 tokenId) external { + _burn(tokenId); + } + +} + +contract TestReversible1155PatchNFT is PatchworkReversible1155Patch { + + uint256 _nextTokenId = 0; + + struct TestReversible1155PatchNFTMetadata { + uint256 thing; + } + + constructor(address manager_) Patchwork721("testscope", "Test1155PatchNFT", "TPLR", manager_) { + } + + function schemaURI() pure external returns (string memory) { + return "https://mything/my-nft-metadata.json"; + } + + function imageURI(uint256 _tokenId) pure external returns (string memory) { + return string(abi.encodePacked("https://mything/nft-", _tokenId)); + } + + function schema() pure external returns (MetadataSchema memory) { + MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](1); + entries[0] = MetadataSchemaEntry(1, 0, FieldType.UINT256, 0, FieldVisibility.PUBLIC, 2, 0, "thing"); + return MetadataSchema(1, entries); + } + + function mintPatch(address to, address originalNFTAddress, uint originalNFTTokenId, address account) external mustBeManager returns (uint256 tokenId){ + // Just for testing + tokenId = _nextTokenId; + _nextTokenId++; + _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, account); _safeMint(to, tokenId); _metadataStorage[tokenId] = new uint256[](1); return tokenId; diff --git a/test/nfts/TestPatchFragmentNFT.sol b/test/nfts/TestPatchFragmentNFT.sol index 4ff3a79..97b4a8c 100644 --- a/test/nfts/TestPatchFragmentNFT.sol +++ b/test/nfts/TestPatchFragmentNFT.sol @@ -22,7 +22,7 @@ struct TestPatchFragmentNFTMetadata { string nickname; } -contract TestPatchFragmentNFT is PatchworkPatch, PatchworkFragmentSingle { +contract TestPatchFragmentNFT is PatchworkReversiblePatch, PatchworkFragmentSingle { uint256 _nextTokenId; @@ -30,9 +30,9 @@ contract TestPatchFragmentNFT is PatchworkPatch, PatchworkFragmentSingle { } // ERC-165 - function supportsInterface(bytes4 interfaceID) public view virtual override(PatchworkPatch, PatchworkFragmentSingle) returns (bool) { + function supportsInterface(bytes4 interfaceID) public view virtual override(PatchworkReversiblePatch, PatchworkFragmentSingle) returns (bool) { return PatchworkFragmentSingle.supportsInterface(interfaceID) || - PatchworkPatch.supportsInterface(interfaceID); + PatchworkReversiblePatch.supportsInterface(interfaceID); } function schemaURI() pure external override returns (string memory) { @@ -58,7 +58,7 @@ contract TestPatchFragmentNFT is PatchworkPatch, PatchworkFragmentSingle { return PatchworkPatch.ownerOf(tokenId); } - function updateOwnership(uint256 tokenId) public virtual override(PatchworkPatch, PatchworkFragmentSingle) { + function updateOwnership(uint256 tokenId) public virtual override(IPatchworkPatch, PatchworkPatch, PatchworkFragmentSingle) { PatchworkPatch.updateOwnership(tokenId); } @@ -121,7 +121,7 @@ contract TestPatchFragmentNFT is PatchworkPatch, PatchworkFragmentSingle { // Just for testing tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, true); + _storePatch(tokenId, originalNFTAddress, originalNFTTokenId); _safeMint(originalNFTOwner, tokenId); _metadataStorage[tokenId] = new uint256[](3); return tokenId; @@ -132,7 +132,7 @@ contract TestPatchFragmentNFT is PatchworkPatch, PatchworkFragmentSingle { _burn(tokenId); } - function _burn(uint256 tokenId) internal virtual override(PatchworkPatch, ERC721) { - return PatchworkPatch._burn(tokenId); + function _burn(uint256 tokenId) internal virtual override(PatchworkReversiblePatch, ERC721) { + return PatchworkReversiblePatch._burn(tokenId); } } \ No newline at end of file diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index 9cf4dab..e1c209e 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -181,7 +181,7 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { // Just for testing tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, false); + _storePatch(tokenId, originalNFTAddress, originalNFTTokenId); _safeMint(owner, tokenId); _metadataStorage[tokenId] = new uint256[](3); return tokenId; diff --git a/test/nfts/TestPatchNFT.sol b/test/nfts/TestPatchNFT.sol index 10f9335..ff5b376 100644 --- a/test/nfts/TestPatchNFT.sol +++ b/test/nfts/TestPatchNFT.sol @@ -91,7 +91,7 @@ contract TestPatchNFT is PatchworkPatch { // Just for testing tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, false); + _storePatch(tokenId, originalNFTAddress, originalNFTTokenId); _safeMint(owner, tokenId); _metadataStorage[tokenId] = new uint256[](1); return tokenId; From eeac2172ff44b581fcfcfe3dc59fa82ba8c5da26 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Sat, 13 Jan 2024 15:02:23 -0800 Subject: [PATCH 51/63] added some indices to events (#74) --- src/IERC4906.sol | 4 ++-- src/IERC5192.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/IERC4906.sol b/src/IERC4906.sol index 002be59..dd2cb23 100644 --- a/src/IERC4906.sol +++ b/src/IERC4906.sol @@ -9,10 +9,10 @@ interface IERC4906 is IERC165, IERC721 { /// @dev This event emits when the metadata of a token is changed. /// So that the third-party platforms such as NFT market could /// timely update the images and related attributes of the NFT. - event MetadataUpdate(uint256 _tokenId); + event MetadataUpdate(uint256 indexed _tokenId); /// @dev This event emits when the metadata of a range of tokens is changed. /// So that the third-party platforms such as NFT market could /// timely update the images and related attributes of the NFTs. - event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); + event BatchMetadataUpdate(uint256 indexed _fromTokenId, uint256 indexed _toTokenId); } \ No newline at end of file diff --git a/src/IERC5192.sol b/src/IERC5192.sol index 2d49971..db3110f 100644 --- a/src/IERC5192.sol +++ b/src/IERC5192.sol @@ -5,12 +5,12 @@ interface IERC5192 { /// @notice Emitted when the locking status is changed to locked. /// @dev If a token is minted and the status is locked, this event should be emitted. /// @param tokenId The identifier for a token. - event Locked(uint256 tokenId); + event Locked(uint256 indexed tokenId); /// @notice Emitted when the locking status is changed to unlocked. /// @dev If a token is minted and the status is unlocked, this event should be emitted. /// @param tokenId The identifier for a token. - event Unlocked(uint256 tokenId); + event Unlocked(uint256 indexed tokenId); /// @notice Returns the locking status of an Soulbound Token /// @dev SBTs assigned to zero address are considered invalid, and queries From d0b144f5406950b0e500d6b61d909463816034d6 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Mon, 15 Jan 2024 17:25:09 -0800 Subject: [PATCH 52/63] Some natspec improvements (#75) --- src/PatchworkFragmentMulti.sol | 2 +- src/PatchworkProtocol.sol | 2 ++ src/PatchworkUtils.sol | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/PatchworkFragmentMulti.sol b/src/PatchworkFragmentMulti.sol index 5b97b60..36e52e9 100644 --- a/src/PatchworkFragmentMulti.sol +++ b/src/PatchworkFragmentMulti.sol @@ -6,7 +6,7 @@ import "./IPatchworkMultiAssignable.sol"; /** @title PatchworkFragmentMulti -@dev base implementation of a Single-relation Fragment is IPatchworkAssignable +@dev base implementation of a Multi-relation Fragment is IPatchworkAssignable */ abstract contract PatchworkFragmentMulti is Patchwork721, IPatchworkMultiAssignable { diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 6fcb8d4..92115da 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -879,6 +879,8 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @param fragmentTokenId the IPatchworkAssignable's tokenId @param target the IPatchworkLiteRef target's address @param targetTokenId the IPatchworkLiteRef target's tokenId + @param direct If this is calling the direct function + @param targetMetadataId the metadataId to use on the target @param scopeName the name of the target's scope */ function _doUnassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool direct, uint256 targetMetadataId, string memory scopeName) private { diff --git a/src/PatchworkUtils.sol b/src/PatchworkUtils.sol index b9a95b9..5ad07b4 100644 --- a/src/PatchworkUtils.sol +++ b/src/PatchworkUtils.sol @@ -20,7 +20,7 @@ library PatchworkUtils { } /** - @notice Converts uint64 raw data to a 16 character string + @notice Converts uint128 raw data to a 16 character string @param raw the raw data @return out the string */ @@ -34,7 +34,7 @@ library PatchworkUtils { } /** - @notice Converts uint64 raw data to a 32 character string + @notice Converts uint256 raw data to a 32 character string @param raw the raw data @return out the string */ From a53f7e459e3e5c01bf453dd2f0baab012e601a5d Mon Sep 17 00:00:00 2001 From: Rob Green Date: Mon, 15 Jan 2024 17:25:39 -0800 Subject: [PATCH 53/63] Several improvement requests (#76) * Moved interfaces and hopefully fixed payable critical error * Refactoring patch targets and payables * Seeing if this helps Zeppelin Defender calm down * All patches payable so they can proxy from a public call * Standardizing patch targets across types * More standardizing * Patch field renaming * Make manager immutable --- src/IPatchwork1155Patch.sol | 38 ------------- src/Patchwork1155Patch.sol | 54 +++++++----------- src/Patchwork721.sol | 8 +-- src/PatchworkAccountPatch.sol | 36 ++++++------ src/PatchworkFragmentMulti.sol | 2 +- src/PatchworkFragmentSingle.sol | 2 +- src/PatchworkLiteRef.sol | 4 +- src/PatchworkPatch.sol | 56 ++++++++----------- src/PatchworkProtocol.sol | 31 +++++----- src/{ => interfaces}/IERC4906.sol | 0 src/{ => interfaces}/IERC5192.sol | 0 src/interfaces/IPatchwork1155Patch.sol | 41 ++++++++++++++ src/{ => interfaces}/IPatchwork721.sol | 0 .../IPatchworkAccountPatch.sol | 8 +-- src/{ => interfaces}/IPatchworkAssignable.sol | 0 src/{ => interfaces}/IPatchworkLiteRef.sol | 0 src/{ => interfaces}/IPatchworkMintable.sol | 0 .../IPatchworkMultiAssignable.sol | 0 src/{ => interfaces}/IPatchworkPatch.sol | 20 ++++--- src/{ => interfaces}/IPatchworkProtocol.sol | 0 src/{ => interfaces}/IPatchworkScoped.sol | 0 .../IPatchworkSingleAssignable.sol | 0 test/Patchwork1155Patch.t.sol | 10 ++-- test/PatchworkAccountPatch.t.sol | 2 +- test/PatchworkPatch.t.sol | 2 +- test/PatchworkProtocol.t.sol | 4 +- test/nfts/Test1155PatchNFT.sol | 14 +++-- test/nfts/TestAccountPatchNFT.sol | 5 +- test/nfts/TestDynamicArrayLiteRefNFT.sol | 13 +++-- test/nfts/TestFragmentLiteRefNFT.sol | 8 ++- test/nfts/TestFragmentSingleNFT.sol | 5 -- test/nfts/TestMultiFragmentNFT.sol | 8 ++- test/nfts/TestPatchFragmentNFT.sol | 12 ++-- test/nfts/TestPatchLiteRefNFT.sol | 13 ++--- test/nfts/TestPatchNFT.sol | 14 ++--- test/nfts/TestPatchworkNFT.sol | 8 ++- 36 files changed, 206 insertions(+), 212 deletions(-) delete mode 100644 src/IPatchwork1155Patch.sol rename src/{ => interfaces}/IERC4906.sol (100%) rename src/{ => interfaces}/IERC5192.sol (100%) create mode 100644 src/interfaces/IPatchwork1155Patch.sol rename src/{ => interfaces}/IPatchwork721.sol (100%) rename src/{ => interfaces}/IPatchworkAccountPatch.sol (73%) rename src/{ => interfaces}/IPatchworkAssignable.sol (100%) rename src/{ => interfaces}/IPatchworkLiteRef.sol (100%) rename src/{ => interfaces}/IPatchworkMintable.sol (100%) rename src/{ => interfaces}/IPatchworkMultiAssignable.sol (100%) rename src/{ => interfaces}/IPatchworkPatch.sol (67%) rename src/{ => interfaces}/IPatchworkProtocol.sol (100%) rename src/{ => interfaces}/IPatchworkScoped.sol (100%) rename src/{ => interfaces}/IPatchworkSingleAssignable.sol (100%) diff --git a/src/IPatchwork1155Patch.sol b/src/IPatchwork1155Patch.sol deleted file mode 100644 index f49f436..0000000 --- a/src/IPatchwork1155Patch.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -import "./IPatchworkScoped.sol"; - -/** -@title Patchwork Protocol 1155 Patch Interface -@author Runic Labs, Inc -@notice Interface for contracts supporting Patchwork patch standard -*/ -interface IPatchwork1155Patch is IPatchworkScoped { - /** - @notice Creates a new token for the owner, representing a patch - @param to Address of the owner of the patch token - @param originalAddress Address of the original 1155 - @param originalTokenId ID of the original 1155 token - @param originalAccount Address of the original 1155 account - @return tokenId ID of the newly minted token - */ - function mintPatch(address to, address originalAddress, uint256 originalTokenId, address originalAccount) external returns (uint256 tokenId); -} - -/** -@title Patchwork Protocol Reversible 1155 Patch Interface -@author Runic Labs, Inc -@notice Interface for contracts supporting Patchwork patch standard with reverse lookup -*/ -interface IPatchworkReversible1155Patch is IPatchwork1155Patch { - /** - @notice Returns the token ID (if it exists) for an NFT that may have been patched - @dev Requires reverse storage enabled - @param originalAddress Address of the original 1155 - @param originalTokenId ID of the original 1155 token - @param originalAccount Address of the original 1155 account - @return tokenId ID of the newly minted token - */ - function getTokenIdForOriginal1155(address originalAddress, uint256 originalTokenId, address originalAccount) external returns (uint256 tokenId); -} \ No newline at end of file diff --git a/src/Patchwork1155Patch.sol b/src/Patchwork1155Patch.sol index f411d4c..c7b605e 100644 --- a/src/Patchwork1155Patch.sol +++ b/src/Patchwork1155Patch.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.13; import "./Patchwork721.sol"; -import "./IPatchwork1155Patch.sol"; +import "./interfaces/IPatchwork1155Patch.sol"; /** @title Patchwork1155Patch @@ -11,15 +11,8 @@ import "./IPatchwork1155Patch.sol"; */ abstract contract Patchwork1155Patch is Patchwork721, IPatchwork1155Patch { - /// @dev A canonical path to an 1155 patched - struct PatchCanonical { - address addr; // The address of the 1155 - uint256 tokenId; // The tokenId of the 1155 - address account; // The account for the 1155 - } - /// @dev Mapping from token ID to the canonical address of the NFT that this patch is applied to. - mapping(uint256 => PatchCanonical) internal _patchedAddresses; + mapping(uint256 => PatchTarget) internal _targetsById; /** @dev See {IERC165-supportsInterface} @@ -32,24 +25,22 @@ abstract contract Patchwork1155Patch is Patchwork721, IPatchwork1155Patch { /** @notice stores a patch @param tokenId the tokenId of the patch - @param originalAddress the address of the original ERC-1155 we are patching - @param originalTokenId the tokenId of the original ERC-1155 we are patching - @param account the account of the ERC-1155 we are patching + @param target the patch target */ - function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId, address account) internal virtual { - _patchedAddresses[tokenId] = PatchCanonical(originalAddress, originalTokenId, account); + function _storePatch(uint256 tokenId, PatchTarget memory target) internal virtual { + _targetsById[tokenId] = target; } /** @dev See {ERC721-_burn} */ function _burn(uint256 tokenId) internal virtual override { - PatchCanonical storage canonical = _patchedAddresses[tokenId]; - address originalAddress = canonical.addr; - uint256 originalTokenId = canonical.tokenId; - address account = canonical.account; + PatchTarget storage target = _targetsById[tokenId]; + address originalAddress = target.addr; + uint256 originalTokenId = target.tokenId; + address account = target.account; IPatchworkProtocol(_manager).patchBurned1155(originalAddress, originalTokenId, account, address(this)); - delete _patchedAddresses[tokenId]; + delete _targetsById[tokenId]; super._burn(tokenId); } } @@ -60,7 +51,7 @@ abstract contract Patchwork1155Patch is Patchwork721, IPatchwork1155Patch { */ abstract contract PatchworkReversible1155Patch is Patchwork1155Patch, IPatchworkReversible1155Patch { /// @dev Mapping of hash of original address + token ID + account for reverse lookups - mapping(bytes32 => uint256) internal _patchedAddressesRev; // hash of patched addr+tokenid+account to tokenId + mapping(bytes32 => uint256) internal _idsByTargetHash; // hash of patched addr+tokenid+account to tokenId /** @dev See {IERC165-supportsInterface} @@ -71,33 +62,28 @@ abstract contract PatchworkReversible1155Patch is Patchwork1155Patch, IPatchwork } /** - @dev See {IPatchwork1155Patch-getTokenIdForOriginal1155} + @dev See {IPatchwork1155Patch-getTokenIdByTarget} */ - function getTokenIdForOriginal1155(address originalAddress, uint256 originalTokenId, address originalAccount) public view virtual returns (uint256 tokenId) { - return _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId, originalAccount))]; + function getTokenIdByTarget(PatchTarget memory target) public view virtual returns (uint256 tokenId) { + return _idsByTargetHash[keccak256(abi.encode(target))]; } /** @notice stores a patch @param tokenId the tokenId of the patch - @param originalAddress the address of the original ERC-1155 we are patching - @param originalTokenId the tokenId of the original ERC-1155 we are patching - @param account the account of the ERC-1155 we are patching + @param target the patch target */ - function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId, address account) internal virtual override { - _patchedAddresses[tokenId] = PatchCanonical(originalAddress, originalTokenId, account); - _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId, account))] = tokenId; + function _storePatch(uint256 tokenId, PatchTarget memory target) internal virtual override { + _targetsById[tokenId] = target; + _idsByTargetHash[keccak256(abi.encode(target))] = tokenId; } /** @dev See {ERC721-_burn} */ function _burn(uint256 tokenId) internal virtual override { - PatchCanonical storage canonical = _patchedAddresses[tokenId]; - address originalAddress = canonical.addr; - uint256 originalTokenId = canonical.tokenId; - address account = canonical.account; - delete _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId, account))]; + PatchTarget storage target = _targetsById[tokenId]; + delete _idsByTargetHash[keccak256(abi.encode(target))]; super._burn(tokenId); } } \ No newline at end of file diff --git a/src/Patchwork721.sol b/src/Patchwork721.sol index 5ad46c3..f265ce6 100644 --- a/src/Patchwork721.sol +++ b/src/Patchwork721.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.13; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; -import "./IPatchwork721.sol"; -import "./IERC4906.sol"; -import "./IPatchworkProtocol.sol"; +import "./interfaces/IPatchwork721.sol"; +import "./interfaces/IERC4906.sol"; +import "./interfaces/IPatchworkProtocol.sol"; /** @title Patchwork721 Abstract Contract @@ -18,7 +18,7 @@ abstract contract Patchwork721 is ERC721, IPatchwork721, IERC4906, Ownable { string internal _scopeName; /// @dev Our manager (PatchworkProtocol). - address internal _manager; + address internal immutable _manager; /// @dev A mapping to keep track of permissions for each address. mapping(address => uint256) internal _permissionsAllow; diff --git a/src/PatchworkAccountPatch.sol b/src/PatchworkAccountPatch.sol index f33db22..4d4fcdc 100644 --- a/src/PatchworkAccountPatch.sol +++ b/src/PatchworkAccountPatch.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "./IPatchworkAccountPatch.sol"; -import "./IPatchworkProtocol.sol"; +import "./interfaces/IPatchworkAccountPatch.sol"; +import "./interfaces/IPatchworkProtocol.sol"; import "./Patchwork721.sol"; /** @@ -13,7 +13,7 @@ import "./Patchwork721.sol"; abstract contract PatchworkAccountPatch is Patchwork721, IPatchworkAccountPatch { /// @dev Mapping from token ID to the address of the NFT that this patch is applied to. - mapping(uint256 => address) internal _patchedAddresses; + mapping(uint256 => address) internal _targetsById; /** @dev See {IERC165-supportsInterface} @@ -26,20 +26,20 @@ abstract contract PatchworkAccountPatch is Patchwork721, IPatchworkAccountPatch /** @notice stores a patch @param tokenId the tokenId of the patch - @param originalAccountAddress the account we are patching + @param target the account we are patching */ - function _storePatch(uint256 tokenId, address originalAccountAddress) internal virtual { + function _storePatch(uint256 tokenId, address target) internal virtual { // PatchworkProtocol handles uniqueness assertion - _patchedAddresses[tokenId] = originalAccountAddress; + _targetsById[tokenId] = target; } /** @dev See {ERC721-_burn} */ function _burn(uint256 tokenId) internal virtual override { - address originalAddress = _patchedAddresses[tokenId]; + address originalAddress = _targetsById[tokenId]; IPatchworkProtocol(_manager).patchBurnedAccount(originalAddress, address(this)); - delete _patchedAddresses[tokenId]; + delete _targetsById[tokenId]; super._burn(tokenId); } @@ -51,7 +51,7 @@ abstract contract PatchworkAccountPatch is Patchwork721, IPatchworkAccountPatch */ abstract contract PatchworkReversibleAccountPatch is PatchworkAccountPatch, IPatchworkReversibleAccountPatch { /// @dev Mapping of original address to token Ids for reverse lookups - mapping(address => uint256) internal _patchedAddressesRev; + mapping(address => uint256) internal _idsByTarget; /** @dev See {IERC165-supportsInterface} @@ -62,29 +62,29 @@ abstract contract PatchworkReversibleAccountPatch is PatchworkAccountPatch, IPat } /** - @dev See {IPatchworkAccountPatch-getTokenIdForOriginalAccount} + @dev See {IPatchworkAccountPatch-getTokenIdByTarget} */ - function getTokenIdForOriginalAccount(address originalAddress) public view virtual returns (uint256 tokenId) { - return _patchedAddressesRev[originalAddress]; + function getTokenIdByTarget(address target) public view virtual returns (uint256 tokenId) { + return _idsByTarget[target]; } /** @notice stores a patch @param tokenId the tokenId of the patch - @param originalAccountAddress the account we are patching + @param target the account we are patching */ - function _storePatch(uint256 tokenId, address originalAccountAddress) internal virtual override { + function _storePatch(uint256 tokenId, address target) internal virtual override { // PatchworkProtocol handles uniqueness assertion - _patchedAddresses[tokenId] = originalAccountAddress; - _patchedAddressesRev[originalAccountAddress] = tokenId; + _targetsById[tokenId] = target; + _idsByTarget[target] = tokenId; } /** @dev See {ERC721-_burn} */ function _burn(uint256 tokenId) internal virtual override { - address originalAddress = _patchedAddresses[tokenId]; - delete _patchedAddressesRev[originalAddress]; + address target = _targetsById[tokenId]; + delete _idsByTarget[target]; super._burn(tokenId); } } \ No newline at end of file diff --git a/src/PatchworkFragmentMulti.sol b/src/PatchworkFragmentMulti.sol index 36e52e9..02f0c14 100644 --- a/src/PatchworkFragmentMulti.sol +++ b/src/PatchworkFragmentMulti.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.13; import "./Patchwork721.sol"; -import "./IPatchworkMultiAssignable.sol"; +import "./interfaces/IPatchworkMultiAssignable.sol"; /** @title PatchworkFragmentMulti diff --git a/src/PatchworkFragmentSingle.sol b/src/PatchworkFragmentSingle.sol index 0c89149..d174c4d 100644 --- a/src/PatchworkFragmentSingle.sol +++ b/src/PatchworkFragmentSingle.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.13; import "./Patchwork721.sol"; -import "./IPatchworkSingleAssignable.sol"; +import "./interfaces/IPatchworkSingleAssignable.sol"; /** @title PatchworkFragmentSingle diff --git a/src/PatchworkLiteRef.sol b/src/PatchworkLiteRef.sol index 5077156..9da2bd1 100644 --- a/src/PatchworkLiteRef.sol +++ b/src/PatchworkLiteRef.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.13; import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "./IPatchworkLiteRef.sol"; -import "./IPatchworkProtocol.sol"; +import "./interfaces/IPatchworkLiteRef.sol"; +import "./interfaces/IPatchworkProtocol.sol"; /** @title PatchworkLiteRef diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index 0183f62..285b0be 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.13; import "./Patchwork721.sol"; -import "./IPatchworkPatch.sol"; +import "./interfaces/IPatchworkPatch.sol"; /** @title PatchworkPatch @@ -12,14 +12,8 @@ import "./IPatchworkPatch.sol"; */ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { - /// @dev A canonical path to an 721 patched - struct PatchCanonical { - address addr; // The address of the 721 - uint256 tokenId; // The tokenId of the 721 - } - /// @dev Mapping from token ID to the canonical address and tokenId of the NFT that this patch is applied to. - mapping(uint256 => PatchCanonical) internal _patchedAddresses; + mapping(uint256 => PatchTarget) internal _targetsById; /** @dev See {IERC165-supportsInterface} @@ -35,25 +29,24 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { */ function ownerOf(uint256 tokenId) public view virtual override(ERC721, IERC721) returns (address) { // Default is inherited ownership - PatchCanonical storage canonical = _patchedAddresses[tokenId]; - return IERC721(canonical.addr).ownerOf(canonical.tokenId); + PatchTarget storage target = _targetsById[tokenId]; + return IERC721(target.addr).ownerOf(target.tokenId); } /** @notice stores a patch @param tokenId the tokenId of the patch - @param originalAddress the address of the original ERC-721 we are patching - @param originalTokenId the tokenId of the original ERC-721 we are patching + @param target the target 721 being patched */ - function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId) internal virtual { - _patchedAddresses[tokenId] = PatchCanonical(originalAddress, originalTokenId); + function _storePatch(uint256 tokenId, PatchTarget memory target) internal virtual { + _targetsById[tokenId] = target; } /** @dev See {IPatchworkPatch-updateOwnership} */ function updateOwnership(uint256 tokenId) public virtual { - address patchedAddr = _patchedAddresses[tokenId].addr; + address patchedAddr = _targetsById[tokenId].addr; if (patchedAddr != address(0)) { address owner_ = ownerOf(tokenId); address curOwner = super.ownerOf(tokenId); @@ -65,10 +58,10 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { } /** - @dev See {IPatchworkPatch-unpatchedOwnerOf} + @dev See {IPatchworkPatch-ownerOfPatch} */ - function unpatchedOwnerOf(uint256 tokenId) public virtual view returns (address) { - return super.ownerOf(tokenId); + function ownerOfPatch(uint256 tokenId) public virtual view returns (address) { + return ERC721.ownerOf(tokenId); } /** @@ -91,16 +84,16 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { @dev See {ERC721-_burn} */ function _burn(uint256 tokenId) internal virtual override { - PatchCanonical storage canonical = _patchedAddresses[tokenId]; - IPatchworkProtocol(_manager).patchBurned(canonical.addr, canonical.tokenId, address(this)); - delete _patchedAddresses[tokenId]; + PatchTarget storage target = _targetsById[tokenId]; + IPatchworkProtocol(_manager).patchBurned(target.addr, target.tokenId, address(this)); + delete _targetsById[tokenId]; super._burn(tokenId); } } abstract contract PatchworkReversiblePatch is PatchworkPatch, IPatchworkReversiblePatch { /// @dev Mapping of hash of original address + token ID for reverse lookups - mapping(bytes32 => uint256) internal _patchedAddressesRev; // hash of patched addr+tokenid to tokenId + mapping(bytes32 => uint256) internal _idsByTargetHash; // hash of patched addr+tokenid to tokenId /** @dev See {IERC165-supportsInterface} @@ -111,29 +104,28 @@ abstract contract PatchworkReversiblePatch is PatchworkPatch, IPatchworkReversib } /** - @dev See {IPatchworkPatch-getTokenIdForOriginal721} + @dev See {IPatchworkPatch-getTokenIdByTarget} */ - function getTokenIdForOriginal721(address originalAddress, uint256 originalTokenId) public view virtual returns (uint256 tokenId) { - return _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))]; + function getTokenIdByTarget(PatchTarget memory target) public view virtual returns (uint256 tokenId) { + return _idsByTargetHash[keccak256(abi.encode(target))]; } /** @notice stores a patch @param tokenId the tokenId of the patch - @param originalAddress the address of the original ERC-721 we are patching - @param originalTokenId the tokenId of the original ERC-721 we are patching + @param target the target 721 being patched */ - function _storePatch(uint256 tokenId, address originalAddress, uint256 originalTokenId) internal virtual override { - super._storePatch(tokenId, originalAddress, originalTokenId); - _patchedAddressesRev[keccak256(abi.encodePacked(originalAddress, originalTokenId))] = tokenId; + function _storePatch(uint256 tokenId, PatchTarget memory target) internal virtual override { + super._storePatch(tokenId, target); + _idsByTargetHash[keccak256(abi.encode(target))] = tokenId; } /** @dev See {ERC721-_burn} */ function _burn(uint256 tokenId) internal virtual override { - PatchCanonical storage canonical = _patchedAddresses[tokenId]; - delete _patchedAddressesRev[keccak256(abi.encodePacked(canonical.addr, canonical.tokenId))]; + PatchTarget storage target = _targetsById[tokenId]; + delete _idsByTargetHash[keccak256(abi.encode(target))]; super._burn(tokenId); } } \ No newline at end of file diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 92115da..5b7f8ec 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -18,16 +18,16 @@ pragma solidity ^0.8.13; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import "./IPatchwork721.sol"; -import "./IPatchworkSingleAssignable.sol"; -import "./IPatchworkMultiAssignable.sol"; -import "./IPatchworkLiteRef.sol"; -import "./IPatchworkPatch.sol"; -import "./IPatchwork1155Patch.sol"; -import "./IPatchworkAccountPatch.sol"; -import "./IPatchworkProtocol.sol"; -import "./IPatchworkMintable.sol"; -import "./IPatchworkScoped.sol"; +import "./interfaces/IPatchwork721.sol"; +import "./interfaces/IPatchworkSingleAssignable.sol"; +import "./interfaces/IPatchworkMultiAssignable.sol"; +import "./interfaces/IPatchworkLiteRef.sol"; +import "./interfaces/IPatchworkPatch.sol"; +import "./interfaces/IPatchwork1155Patch.sol"; +import "./interfaces/IPatchworkAccountPatch.sol"; +import "./interfaces/IPatchworkProtocol.sol"; +import "./interfaces/IPatchworkMintable.sol"; +import "./interfaces/IPatchworkScoped.sol"; /** @title Patchwork Protocol @@ -75,9 +75,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { /// How much time must elapse before a fee change can be committed (1209600 = 2 weeks) uint256 public constant FEE_CHANGE_TIMELOCK = 1209600; - // TODO maybe not necessary - uint256 public constant TRANSFER_GAS_LIMIT = 5000; - /// The denominator for fee basis points uint256 private constant FEE_BASIS_DENOM = 10000; @@ -299,7 +296,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { // modify state before calling to send scope.balance -= amount; // transfer funds - // (bool sent,) = msg.sender.call{value: amount, gas: TRANSFER_GAS_LIMIT}(""); // TODO is gas limit good or bad? (bool sent,) = msg.sender.call{value: amount}(""); if (!sent) { revert FailedToSend(); @@ -476,8 +472,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { revert InsufficientFunds(); } _protocolBalance -= amount; - // (bool sent,) = msg.sender.call{value: amount, gas: TRANSFER_GAS_LIMIT}(""); // TODO is gas limit good or bad? - (bool sent,) = msg.sender.call{value: amount}(""); + (bool sent,) = msg.sender.call{value: amount}(""); if (!sent) { revert FailedToSend(); } @@ -533,7 +528,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { revert AlreadyPatched(originalAddress, originalTokenId, patchAddress); } _uniquePatches[_hash] = true; - tokenId = patch_.mintPatch(owner, originalAddress, originalTokenId); + tokenId = patch_.mintPatch(owner, IPatchworkPatch.PatchTarget(originalAddress, originalTokenId)); emit Patch(owner, originalAddress, originalTokenId, patchAddress, tokenId); return tokenId; } @@ -571,7 +566,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { revert ERC1155AlreadyPatched(originalAddress, originalTokenId, originalAccount, patchAddress); } _uniquePatches[_hash] = true; - tokenId = patch_.mintPatch(to, originalAddress, originalTokenId, originalAccount); + tokenId = patch_.mintPatch(to, IPatchwork1155Patch.PatchTarget(originalAddress, originalTokenId, originalAccount)); emit ERC1155Patch(to, originalAddress, originalTokenId, originalAccount, patchAddress, tokenId); return tokenId; } diff --git a/src/IERC4906.sol b/src/interfaces/IERC4906.sol similarity index 100% rename from src/IERC4906.sol rename to src/interfaces/IERC4906.sol diff --git a/src/IERC5192.sol b/src/interfaces/IERC5192.sol similarity index 100% rename from src/IERC5192.sol rename to src/interfaces/IERC5192.sol diff --git a/src/interfaces/IPatchwork1155Patch.sol b/src/interfaces/IPatchwork1155Patch.sol new file mode 100644 index 0000000..908e4b3 --- /dev/null +++ b/src/interfaces/IPatchwork1155Patch.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "./IPatchworkScoped.sol"; + +/** +@title Patchwork Protocol 1155 Patch Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting Patchwork patch standard +*/ +interface IPatchwork1155Patch is IPatchworkScoped { + /// @dev A canonical path to an 1155 patched target + struct PatchTarget { + address addr; // The address of the 1155 + uint256 tokenId; // The tokenId of the 1155 + address account; // The account for the 1155 + } + + /** + @notice Creates a new token for the owner, representing a patch + @param to Address of the owner of the patch token + @param target Path to an 1155 to patch + @return tokenId ID of the newly minted token + */ + function mintPatch(address to, PatchTarget memory target) external payable returns (uint256 tokenId); +} + +/** +@title Patchwork Protocol Reversible 1155 Patch Interface +@author Runic Labs, Inc +@notice Interface for contracts supporting Patchwork patch standard with reverse lookup +*/ +interface IPatchworkReversible1155Patch is IPatchwork1155Patch { + /** + @notice Returns the token ID (if it exists) for an 1155 that may have been patched + @dev Requires reverse storage enabled + @param target The 1155 target that was patched + @return tokenId token ID of the patch + */ + function getTokenIdByTarget(PatchTarget memory target) external returns (uint256 tokenId); +} \ No newline at end of file diff --git a/src/IPatchwork721.sol b/src/interfaces/IPatchwork721.sol similarity index 100% rename from src/IPatchwork721.sol rename to src/interfaces/IPatchwork721.sol diff --git a/src/IPatchworkAccountPatch.sol b/src/interfaces/IPatchworkAccountPatch.sol similarity index 73% rename from src/IPatchworkAccountPatch.sol rename to src/interfaces/IPatchworkAccountPatch.sol index cfd53b5..5045a68 100644 --- a/src/IPatchworkAccountPatch.sol +++ b/src/interfaces/IPatchworkAccountPatch.sol @@ -12,10 +12,10 @@ interface IPatchworkAccountPatch is IPatchworkScoped { /** @notice Creates a new token for the owner, representing a patch @param owner Address of the owner of the token - @param originalAccountAddress Address of the original account + @param target Address of the original account @return tokenId ID of the newly minted token */ - function mintPatch(address owner, address originalAccountAddress) external returns (uint256 tokenId); + function mintPatch(address owner, address target) external payable returns (uint256 tokenId); } /** @@ -27,8 +27,8 @@ interface IPatchworkReversibleAccountPatch is IPatchworkAccountPatch { /** @notice Returns the token ID (if it exists) for an NFT that may have been patched @dev Requires reverse storage enabled - @param originalAddress Address of the original account + @param target Address of the original account @return tokenId ID of the newly minted token */ - function getTokenIdForOriginalAccount(address originalAddress) external returns (uint256 tokenId); + function getTokenIdByTarget(address target) external returns (uint256 tokenId); } \ No newline at end of file diff --git a/src/IPatchworkAssignable.sol b/src/interfaces/IPatchworkAssignable.sol similarity index 100% rename from src/IPatchworkAssignable.sol rename to src/interfaces/IPatchworkAssignable.sol diff --git a/src/IPatchworkLiteRef.sol b/src/interfaces/IPatchworkLiteRef.sol similarity index 100% rename from src/IPatchworkLiteRef.sol rename to src/interfaces/IPatchworkLiteRef.sol diff --git a/src/IPatchworkMintable.sol b/src/interfaces/IPatchworkMintable.sol similarity index 100% rename from src/IPatchworkMintable.sol rename to src/interfaces/IPatchworkMintable.sol diff --git a/src/IPatchworkMultiAssignable.sol b/src/interfaces/IPatchworkMultiAssignable.sol similarity index 100% rename from src/IPatchworkMultiAssignable.sol rename to src/interfaces/IPatchworkMultiAssignable.sol diff --git a/src/IPatchworkPatch.sol b/src/interfaces/IPatchworkPatch.sol similarity index 67% rename from src/IPatchworkPatch.sol rename to src/interfaces/IPatchworkPatch.sol index b420327..f85bdc8 100644 --- a/src/IPatchworkPatch.sol +++ b/src/interfaces/IPatchworkPatch.sol @@ -9,14 +9,19 @@ import "./IPatchworkScoped.sol"; @notice Interface for contracts supporting Patchwork patch standard */ interface IPatchworkPatch is IPatchworkScoped { + /// @dev A canonical path to an 721 patched + struct PatchTarget { + address addr; // The address of the 721 + uint256 tokenId; // The tokenId of the 721 + } + /** @notice Creates a new token for the owner, representing a patch @param owner Address of the owner of the token - @param originalAddress Address of the original 721 - @param originalTokenId The original 721's tokenId + @param target path to target of patch @return tokenId ID of the newly minted token */ - function mintPatch(address owner, address originalAddress, uint256 originalTokenId) external returns (uint256 tokenId); + function mintPatch(address owner, PatchTarget memory target) external payable returns (uint256 tokenId); /** @notice Updates the real underlying ownership of a token in storage (if different from current) @@ -29,7 +34,7 @@ interface IPatchworkPatch is IPatchworkScoped { @param tokenId ID of the token @return address Address of the owner */ - function unpatchedOwnerOf(uint256 tokenId) external view returns (address); + function ownerOfPatch(uint256 tokenId) external view returns (address); } /** @@ -41,9 +46,8 @@ interface IPatchworkReversiblePatch is IPatchworkPatch { /** @notice Returns the token ID (if it exists) for an NFT that may have been patched @dev Requires reverse storage enabled - @param originalAddress Address of the original 721 - @param originalTokenId The original 721's tokenId - @return tokenId ID of the newly minted token + @param target Patch to target of patch + @return tokenId token ID of the patch */ - function getTokenIdForOriginal721(address originalAddress, uint256 originalTokenId) external view returns (uint256 tokenId); + function getTokenIdByTarget(PatchTarget memory target) external view returns (uint256 tokenId); } \ No newline at end of file diff --git a/src/IPatchworkProtocol.sol b/src/interfaces/IPatchworkProtocol.sol similarity index 100% rename from src/IPatchworkProtocol.sol rename to src/interfaces/IPatchworkProtocol.sol diff --git a/src/IPatchworkScoped.sol b/src/interfaces/IPatchworkScoped.sol similarity index 100% rename from src/IPatchworkScoped.sol rename to src/interfaces/IPatchworkScoped.sol diff --git a/src/IPatchworkSingleAssignable.sol b/src/interfaces/IPatchworkSingleAssignable.sol similarity index 100% rename from src/IPatchworkSingleAssignable.sol rename to src/interfaces/IPatchworkSingleAssignable.sol diff --git a/test/Patchwork1155Patch.t.sol b/test/Patchwork1155Patch.t.sol index ec89144..d84c278 100644 --- a/test/Patchwork1155Patch.t.sol +++ b/test/Patchwork1155Patch.t.sol @@ -72,16 +72,16 @@ contract Patchwork1155PatchTest is Test { vm.stopPrank(); vm.startPrank(address(_prot)); // basic mints should work - test1155PatchNFT.mintPatch(_userAddress, address(base1155), b, _userAddress); + test1155PatchNFT.mintPatch(_userAddress, IPatchwork1155Patch.PatchTarget(address(base1155), b, _userAddress)); // global - test1155PatchNFT.mintPatch(_userAddress, address(base1155), b, address(0)); + test1155PatchNFT.mintPatch(_userAddress, IPatchwork1155Patch.PatchTarget(address(base1155), b, address(0))); vm.stopPrank(); // no auth vm.expectRevert(); - test1155PatchNFT.mintPatch(_userAddress, address(base1155), b, _userAddress); + test1155PatchNFT.mintPatch(_userAddress, IPatchwork1155Patch.PatchTarget(address(base1155), b, _userAddress)); // global vm.expectRevert(); - test1155PatchNFT.mintPatch(_userAddress, address(base1155), b, address(0)); + test1155PatchNFT.mintPatch(_userAddress, IPatchwork1155Patch.PatchTarget(address(base1155), b, address(0))); } function test1155PatchProto() public { @@ -136,6 +136,6 @@ contract Patchwork1155PatchTest is Test { TestBase1155 base1155 = new TestBase1155(); uint256 b = base1155.mint(_userAddress, 1, 5); uint256 pId = _prot.patch1155(_userAddress, address(base1155), b, _userAddress, address(test1155PatchNFT)); - assertEq(pId, test1155PatchNFT.getTokenIdForOriginal1155(address(base1155), b, _userAddress)); + assertEq(pId, test1155PatchNFT.getTokenIdByTarget(IPatchwork1155Patch.PatchTarget(address(base1155), b, _userAddress))); } } \ No newline at end of file diff --git a/test/PatchworkAccountPatch.t.sol b/test/PatchworkAccountPatch.t.sol index cd961b9..0caad5d 100644 --- a/test/PatchworkAccountPatch.t.sol +++ b/test/PatchworkAccountPatch.t.sol @@ -107,6 +107,6 @@ contract PatchworkAccountPatchTest is Test { TestAccountPatchNFT testAccountPatchNFT = new TestAccountPatchNFT(address(_prot), false); // User patching is on uint256 pId = _prot.patchAccount(_userAddress, _user2Address, address(testAccountPatchNFT)); - assertEq(pId, testAccountPatchNFT.getTokenIdForOriginalAccount(_user2Address)); + assertEq(pId, testAccountPatchNFT.getTokenIdByTarget(_user2Address)); } } \ No newline at end of file diff --git a/test/PatchworkPatch.t.sol b/test/PatchworkPatch.t.sol index d9c76fb..635f4dd 100644 --- a/test/PatchworkPatch.t.sol +++ b/test/PatchworkPatch.t.sol @@ -103,7 +103,7 @@ contract PatchworkPatchTest is Test { uint256 liteRefId2 = _prot.patch(_user2Address, address(_testBaseNFT), baseTokenId2, address(_testPatchLiteRefNFT)); uint256 fragmentTokenId = _prot.patch(_userAddress, address(_testBaseNFT), baseTokenId3, address(testPatchFragmentNFT)); // check reverse lookups - assertEq(fragmentTokenId, testPatchFragmentNFT.getTokenIdForOriginal721(address(_testBaseNFT), baseTokenId3)); + assertEq(fragmentTokenId, testPatchFragmentNFT.getTokenIdByTarget(IPatchworkPatch.PatchTarget(address(_testBaseNFT), baseTokenId3))); // cannot assign patch to a literef that this person does not own vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); _prot.assign(address(testPatchFragmentNFT), fragmentTokenId, address(_testPatchLiteRefNFT), liteRefId2); diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index 180c01e..eba958e 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -699,9 +699,9 @@ contract PatchworkProtocolTest is Test { vm.prank(_userAddress); _testBaseNFT.transferFrom(_userAddress, _user2Address, _testBaseNFTTokenId); assertEq(_user2Address, patch.ownerOf(patchTokenId)); - assertEq(_userAddress, patch.unpatchedOwnerOf(patchTokenId)); + assertEq(_userAddress, patch.ownerOfPatch(patchTokenId)); _prot.updateOwnershipTree(address(patch), patchTokenId); - assertEq(_user2Address, patch.unpatchedOwnerOf(patchTokenId)); + assertEq(_user2Address, patch.ownerOfPatch(patchTokenId)); } function testLocks() public { diff --git a/test/nfts/Test1155PatchNFT.sol b/test/nfts/Test1155PatchNFT.sol index c465e9e..8477070 100644 --- a/test/nfts/Test1155PatchNFT.sol +++ b/test/nfts/Test1155PatchNFT.sol @@ -28,11 +28,14 @@ contract Test1155PatchNFT is Patchwork1155Patch { return MetadataSchema(1, entries); } - function mintPatch(address to, address originalNFTAddress, uint originalNFTTokenId, address account) external mustBeManager returns (uint256 tokenId){ + function mintPatch(address to, PatchTarget memory target) external payable mustBeManager returns (uint256 tokenId){ + if (msg.value > 0) { + revert(); + } // Just for testing tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, account); + _storePatch(tokenId, target); _safeMint(to, tokenId); _metadataStorage[tokenId] = new uint256[](1); return tokenId; @@ -69,11 +72,14 @@ contract TestReversible1155PatchNFT is PatchworkReversible1155Patch { return MetadataSchema(1, entries); } - function mintPatch(address to, address originalNFTAddress, uint originalNFTTokenId, address account) external mustBeManager returns (uint256 tokenId){ + function mintPatch(address to, PatchTarget memory target) external payable mustBeManager returns (uint256 tokenId){ // Just for testing + if (msg.value > 0) { + revert(); + } tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, originalNFTAddress, originalNFTTokenId, account); + _storePatch(tokenId, target); _safeMint(to, tokenId); _metadataStorage[tokenId] = new uint256[](1); return tokenId; diff --git a/test/nfts/TestAccountPatchNFT.sol b/test/nfts/TestAccountPatchNFT.sol index 5c67104..7e1cc86 100644 --- a/test/nfts/TestAccountPatchNFT.sol +++ b/test/nfts/TestAccountPatchNFT.sol @@ -30,7 +30,10 @@ contract TestAccountPatchNFT is PatchworkReversibleAccountPatch { return MetadataSchema(1, entries); } - function mintPatch(address to, address original) public mustBeManager returns (uint256) { + function mintPatch(address to, address original) public payable mustBeManager returns (uint256) { + if (msg.value > 0) { + revert(); + } if (_sameOwnerModel) { if (to != original) { revert IPatchworkProtocol.MintNotAllowed(to); diff --git a/test/nfts/TestDynamicArrayLiteRefNFT.sol b/test/nfts/TestDynamicArrayLiteRefNFT.sol index 678b127..20d1612 100644 --- a/test/nfts/TestDynamicArrayLiteRefNFT.sol +++ b/test/nfts/TestDynamicArrayLiteRefNFT.sol @@ -11,7 +11,7 @@ pragma solidity ^0.8.13; import "../../src/Patchwork721.sol"; import "../../src/PatchworkLiteRef.sol"; -import "../../src/IPatchworkMintable.sol"; +import "../../src/interfaces/IPatchworkMintable.sol"; import "forge-std/console.sol"; struct TestDynamicArrayLiteRefNFTMetadata { @@ -50,12 +50,10 @@ contract TestDynamicArrayLiteRefNFT is Patchwork721, PatchworkLiteRef, IPatchwor function imageURI(uint256 _tokenId) pure external override returns (string memory) {} - function setManager(address manager_) external { - require(_checkWriteAuth()); - _manager = manager_; - } - function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { + if (msg.value > 0) { + revert(); + } tokenId = _nextTokenId; _nextTokenId++; _safeMint(to, tokenId); @@ -64,6 +62,9 @@ contract TestDynamicArrayLiteRefNFT is Patchwork721, PatchworkLiteRef, IPatchwor } function mintBatch(address to, bytes calldata data, uint256 quantity) public payable returns (uint256[] memory tokenIds) { + if (msg.value > 0) { + revert(); + } tokenIds = new uint256[](quantity); for (uint256 i = 0; i < quantity; i++) { tokenIds[i] = mint(to, data); diff --git a/test/nfts/TestFragmentLiteRefNFT.sol b/test/nfts/TestFragmentLiteRefNFT.sol index 8fa826b..32a6056 100644 --- a/test/nfts/TestFragmentLiteRefNFT.sol +++ b/test/nfts/TestFragmentLiteRefNFT.sol @@ -11,7 +11,7 @@ pragma solidity ^0.8.13; import "../../src/PatchworkFragmentSingle.sol"; import "../../src/PatchworkLiteRef.sol"; -import "../../src/IPatchworkMintable.sol"; +import "../../src/interfaces/IPatchworkMintable.sol"; enum FragmentType { BASE, @@ -47,6 +47,9 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef, IP } function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { + if (msg.value > 0) { + revert(); + } tokenId = _nextTokenId; _nextTokenId++; _safeMint(to, tokenId); @@ -54,6 +57,9 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef, IP } function mintBatch(address to, bytes calldata data, uint256 quantity) public payable returns (uint256[] memory tokenIds) { + if (msg.value > 0) { + revert(); + } tokenIds = new uint256[](quantity); for (uint256 i = 0; i < quantity; i++) { tokenIds[i] = mint(to, data); diff --git a/test/nfts/TestFragmentSingleNFT.sol b/test/nfts/TestFragmentSingleNFT.sol index a17ca49..810e5f8 100644 --- a/test/nfts/TestFragmentSingleNFT.sol +++ b/test/nfts/TestFragmentSingleNFT.sol @@ -34,11 +34,6 @@ contract TestFragmentSingleNFT is PatchworkFragmentSingle { } function imageURI(uint256 _tokenId) pure external override returns (string memory) {} - - function setManager(address manager_) external { - require(_checkWriteAuth()); - _manager = manager_; - } /* Hard coded prototype schema is: diff --git a/test/nfts/TestMultiFragmentNFT.sol b/test/nfts/TestMultiFragmentNFT.sol index 157c447..5c5e212 100644 --- a/test/nfts/TestMultiFragmentNFT.sol +++ b/test/nfts/TestMultiFragmentNFT.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import "../../src/PatchworkFragmentMulti.sol"; import "../../src/PatchworkLiteRef.sol"; -import "../../src/IPatchworkMintable.sol"; +import "../../src/interfaces/IPatchworkMintable.sol"; struct TestMultiFragmentNFTMetadata { uint8 nothing; @@ -21,6 +21,9 @@ contract TestMultiFragmentNFT is PatchworkFragmentMulti, IPatchworkMintable { } function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { + if (msg.value > 0) { + revert(); + } tokenId = _nextTokenId; _nextTokenId++; _safeMint(to, tokenId); @@ -28,6 +31,9 @@ contract TestMultiFragmentNFT is PatchworkFragmentMulti, IPatchworkMintable { } function mintBatch(address to, bytes calldata data, uint256 quantity) public payable returns (uint256[] memory tokenIds) { + if (msg.value > 0) { + revert(); + } tokenIds = new uint256[](quantity); for (uint256 i = 0; i < quantity; i++) { tokenIds[i] = mint(to, data); diff --git a/test/nfts/TestPatchFragmentNFT.sol b/test/nfts/TestPatchFragmentNFT.sol index 97b4a8c..8aa88d2 100644 --- a/test/nfts/TestPatchFragmentNFT.sol +++ b/test/nfts/TestPatchFragmentNFT.sol @@ -41,11 +41,6 @@ contract TestPatchFragmentNFT is PatchworkReversiblePatch, PatchworkFragmentSing function imageURI(uint256 _tokenId) pure external override returns (string memory) {} - function setManager(address manager_) external { - require(_checkWriteAuth()); - _manager = manager_; - } - function setLocked(uint256 tokenId, bool locked_) public view virtual override(PatchworkPatch, PatchworkFragmentSingle) { return PatchworkPatch.setLocked(tokenId, locked_); } @@ -117,11 +112,14 @@ contract TestPatchFragmentNFT is PatchworkReversiblePatch, PatchworkFragmentSing return unpackMetadata(_metadataStorage[_tokenId]); } - function mintPatch(address originalNFTOwner, address originalNFTAddress, uint originalNFTTokenId) external mustBeManager returns (uint256 tokenId){ + function mintPatch(address originalNFTOwner, PatchTarget memory target) external payable mustBeManager returns (uint256 tokenId){ + if (msg.value > 0) { + revert(); + } // Just for testing tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, originalNFTAddress, originalNFTTokenId); + _storePatch(tokenId, target); _safeMint(originalNFTOwner, tokenId); _metadataStorage[tokenId] = new uint256[](3); return tokenId; diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index e1c209e..c440346 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -42,11 +42,6 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { function imageURI(uint256 _tokenId) pure external override returns (string memory) {} - function setManager(address manager_) external { - require(_checkWriteAuth()); - _manager = manager_; - } - /* Hard coded prototype schema is: slot 0 offset 0 = artifactIDs (spans 2) - also we need special built-in handling for < 256 bit IDs @@ -170,18 +165,18 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { // TODO bulk insert for fewer stores } - function mintPatch(address owner, address originalNFTAddress, uint originalNFTTokenId) external returns (uint256 tokenId){ - if (msg.sender != _manager) { + function mintPatch(address owner, PatchTarget memory target) external payable mustBeManager() returns (uint256 tokenId){ + if (msg.value > 0) { revert(); } // require inherited ownership - if (IERC721(originalNFTAddress).ownerOf(originalNFTTokenId) != owner) { + if (IERC721(target.addr).ownerOf(target.tokenId) != owner) { revert IPatchworkProtocol.NotAuthorized(owner); } // Just for testing tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, originalNFTAddress, originalNFTTokenId); + _storePatch(tokenId, target); _safeMint(owner, tokenId); _metadataStorage[tokenId] = new uint256[](3); return tokenId; diff --git a/test/nfts/TestPatchNFT.sol b/test/nfts/TestPatchNFT.sol index ff5b376..7cf8a1c 100644 --- a/test/nfts/TestPatchNFT.sol +++ b/test/nfts/TestPatchNFT.sol @@ -34,11 +34,6 @@ contract TestPatchNFT is PatchworkPatch { function imageURI(uint256 _tokenId) pure external override returns (string memory) {} - function setManager(address manager_) external { - require(_checkWriteAuth()); - _manager = manager_; - } - function schema() pure external override returns (MetadataSchema memory) { MetadataSchemaEntry[] memory entries = new MetadataSchemaEntry[](7); entries[0] = MetadataSchemaEntry(0, 1, FieldType.UINT16, 1, FieldVisibility.PUBLIC, 0, 0, "xp"); @@ -83,15 +78,18 @@ contract TestPatchNFT is PatchworkPatch { return unpackMetadata(_metadataStorage[_tokenId]); } - function mintPatch(address owner, address originalNFTAddress, uint originalNFTTokenId) external mustBeManager returns (uint256 tokenId){ + function mintPatch(address owner, PatchTarget memory target) external payable mustBeManager returns (uint256 tokenId) { + if (msg.value > 0) { + revert(); + } // require inherited ownership - if (IERC721(originalNFTAddress).ownerOf(originalNFTTokenId) != owner) { + if (IERC721(target.addr).ownerOf(target.tokenId) != owner) { revert IPatchworkProtocol.NotAuthorized(owner); } // Just for testing tokenId = _nextTokenId; _nextTokenId++; - _storePatch(tokenId, originalNFTAddress, originalNFTTokenId); + _storePatch(tokenId, target); _safeMint(owner, tokenId); _metadataStorage[tokenId] = new uint256[](1); return tokenId; diff --git a/test/nfts/TestPatchworkNFT.sol b/test/nfts/TestPatchworkNFT.sol index 6065e13..a426ff7 100644 --- a/test/nfts/TestPatchworkNFT.sol +++ b/test/nfts/TestPatchworkNFT.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.13; import "../../src/Patchwork721.sol"; -import "../../src/IPatchworkMintable.sol"; +import "../../src/interfaces/IPatchworkMintable.sol"; contract TestPatchworkNFT is Patchwork721, IPatchworkMintable { @@ -39,6 +39,9 @@ contract TestPatchworkNFT is Patchwork721, IPatchworkMintable { } function mint(address to, bytes calldata /* data */) public payable returns (uint256 tokenId) { + if (msg.value > 0) { + revert(); + } tokenId = _nextTokenId; _nextTokenId++; _mint(to, tokenId); @@ -46,6 +49,9 @@ contract TestPatchworkNFT is Patchwork721, IPatchworkMintable { } function mintBatch(address to, bytes calldata data, uint256 quantity) public payable returns (uint256[] memory tokenIds) { + if (msg.value > 0) { + revert(); + } tokenIds = new uint256[](quantity); for (uint256 i = 0; i < quantity; i++) { tokenIds[i] = mint(to, data); From 2c1021522780f270e26be87f25f89473564236b1 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Mon, 15 Jan 2024 17:32:08 -0800 Subject: [PATCH 54/63] small fix (#79) --- src/PatchworkProtocol.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 5b7f8ec..08af2c3 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -598,7 +598,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { revert NotAuthorized(msg.sender); } _handlePatchFee(scopeName, scope, patchAddress); - // limit this to one unique patch (originalAddress+TokenID+patchAddress) + // limit this to one unique patch (originalAddress+patchAddress) bytes32 _hash = keccak256(abi.encodePacked(originalAddress, patchAddress)); if (_uniquePatches[_hash]) { revert AccountAlreadyPatched(originalAddress, patchAddress); From cfda9457661305f57c3c8d0297df9aee6ce1bf5c Mon Sep 17 00:00:00 2001 From: Rob Green Date: Wed, 17 Jan 2024 09:15:33 -0800 Subject: [PATCH 55/63] Open zeppelin 5 (#80) * OZ5 * forge install: openzeppelin-foundry-upgrades * forge install: openzeppelin-contracts-upgradeable v5.0.1 * Updates for OpenZeppelin 5 * small refactor --- .gitmodules | 6 +++++ lib/openzeppelin-contracts | 2 +- lib/openzeppelin-contracts-upgradeable | 1 + lib/openzeppelin-foundry-upgrades | 1 + remappings.txt | 4 +++- src/Patchwork1155Patch.sol | 8 +++---- src/Patchwork721.sol | 10 +-------- src/PatchworkAccountPatch.sol | 6 ++--- src/PatchworkPatch.sol | 6 ++--- src/PatchworkProtocol.sol | 4 ++-- test/Fees.t.sol | 4 ++-- test/PatchworkNFT.t.sol | 12 +++++----- test/nfts/Test1155PatchNFT.sol | 4 ++-- test/nfts/TestAccountPatchNFT.sol | 31 +++++++++++++++++--------- test/nfts/TestPatchFragmentNFT.sol | 6 +---- test/nfts/TestPatchLiteRefNFT.sol | 2 +- test/nfts/TestPatchNFT.sol | 2 +- 17 files changed, 59 insertions(+), 50 deletions(-) create mode 160000 lib/openzeppelin-contracts-upgradeable create mode 160000 lib/openzeppelin-foundry-upgrades diff --git a/.gitmodules b/.gitmodules index b60817e..bd36fda 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,3 +6,9 @@ path = lib/forge-std url = https://github.com/foundry-rs/forge-std branch = v1.5.2 +[submodule "lib/openzeppelin-foundry-upgrades"] + path = lib/openzeppelin-foundry-upgrades + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index d00acef..01ef448 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit d00acef4059807535af0bd0dd0ddf619747a044b +Subproject commit 01ef448981be9d20ca85f2faf6ebdf591ce409f3 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..fbdb824 --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit fbdb824a735891908d5588b28e0da5852d7ed7ba diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades new file mode 160000 index 0000000..ba9b426 --- /dev/null +++ b/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit ba9b4269f2d34425dcff4d9d242f398ddcefddef diff --git a/remappings.txt b/remappings.txt index 69b1596..968141a 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,5 @@ -@openzeppelin/=lib/openzeppelin-contracts/ +@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ @patchwork/=src/ forge-std/=lib/forge-std/src/ + diff --git a/src/Patchwork1155Patch.sol b/src/Patchwork1155Patch.sol index c7b605e..f32ecba 100644 --- a/src/Patchwork1155Patch.sol +++ b/src/Patchwork1155Patch.sol @@ -34,14 +34,14 @@ abstract contract Patchwork1155Patch is Patchwork721, IPatchwork1155Patch { /** @dev See {ERC721-_burn} */ - function _burn(uint256 tokenId) internal virtual override { + function _burnPatch(uint256 tokenId) internal virtual { PatchTarget storage target = _targetsById[tokenId]; address originalAddress = target.addr; uint256 originalTokenId = target.tokenId; address account = target.account; IPatchworkProtocol(_manager).patchBurned1155(originalAddress, originalTokenId, account, address(this)); delete _targetsById[tokenId]; - super._burn(tokenId); + _burn(tokenId); } } @@ -81,9 +81,9 @@ abstract contract PatchworkReversible1155Patch is Patchwork1155Patch, IPatchwork /** @dev See {ERC721-_burn} */ - function _burn(uint256 tokenId) internal virtual override { + function _burnPatch(uint256 tokenId) internal virtual override { PatchTarget storage target = _targetsById[tokenId]; delete _idsByTargetHash[keccak256(abi.encode(target))]; - super._burn(tokenId); + _burnPatch(tokenId); } } \ No newline at end of file diff --git a/src/Patchwork721.sol b/src/Patchwork721.sol index f265ce6..d718e32 100644 --- a/src/Patchwork721.sol +++ b/src/Patchwork721.sol @@ -48,7 +48,7 @@ abstract contract Patchwork721 is ERC721, IPatchwork721, IERC4906, Ownable { string memory name_, string memory symbol_, address manager_ - ) ERC721(name_, symbol_) Ownable() { + ) ERC721(name_, symbol_) Ownable(msg.sender) { _scopeName = scopeName_; _manager = manager_; } @@ -125,14 +125,6 @@ abstract contract Patchwork721 is ERC721, IPatchwork721, IERC4906, Ownable { super.transferFrom(from, to, tokenId); } - /** - @dev See {IERC721-safeTransferFrom}. - */ - function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override(ERC721, IERC721) { - IPatchworkProtocol(_manager).applyTransfer(from, to, tokenId); - super.safeTransferFrom(from, to, tokenId); - } - /** @dev See {IERC721-safeTransferFrom}. */ diff --git a/src/PatchworkAccountPatch.sol b/src/PatchworkAccountPatch.sol index 4d4fcdc..9fc454a 100644 --- a/src/PatchworkAccountPatch.sol +++ b/src/PatchworkAccountPatch.sol @@ -36,7 +36,7 @@ abstract contract PatchworkAccountPatch is Patchwork721, IPatchworkAccountPatch /** @dev See {ERC721-_burn} */ - function _burn(uint256 tokenId) internal virtual override { + function _burnPatch(uint256 tokenId) internal virtual { address originalAddress = _targetsById[tokenId]; IPatchworkProtocol(_manager).patchBurnedAccount(originalAddress, address(this)); delete _targetsById[tokenId]; @@ -82,9 +82,9 @@ abstract contract PatchworkReversibleAccountPatch is PatchworkAccountPatch, IPat /** @dev See {ERC721-_burn} */ - function _burn(uint256 tokenId) internal virtual override { + function _burnPatch(uint256 tokenId) internal virtual override { address target = _targetsById[tokenId]; delete _idsByTarget[target]; - super._burn(tokenId); + super._burnPatch(tokenId); } } \ No newline at end of file diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index 285b0be..7b97bf0 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -83,7 +83,7 @@ abstract contract PatchworkPatch is Patchwork721, IPatchworkPatch { /** @dev See {ERC721-_burn} */ - function _burn(uint256 tokenId) internal virtual override { + function _burnPatch(uint256 tokenId) internal virtual { PatchTarget storage target = _targetsById[tokenId]; IPatchworkProtocol(_manager).patchBurned(target.addr, target.tokenId, address(this)); delete _targetsById[tokenId]; @@ -123,9 +123,9 @@ abstract contract PatchworkReversiblePatch is PatchworkPatch, IPatchworkReversib /** @dev See {ERC721-_burn} */ - function _burn(uint256 tokenId) internal virtual override { + function _burnPatch(uint256 tokenId) internal virtual override { PatchTarget storage target = _targetsById[tokenId]; delete _idsByTargetHash[keccak256(abi.encode(target))]; - super._burn(tokenId); + super._burnPatch(tokenId); } } \ No newline at end of file diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 08af2c3..f0f8c03 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -17,7 +17,7 @@ pragma solidity ^0.8.13; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "./interfaces/IPatchwork721.sol"; import "./interfaces/IPatchworkSingleAssignable.sol"; import "./interfaces/IPatchworkMultiAssignable.sol"; @@ -82,7 +82,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { uint256 private constant PROTOCOL_FEE_CEILING = 3000; /// Constructor - constructor() Ownable() ReentrancyGuard() {} + constructor() Ownable(msg.sender) ReentrancyGuard() {} /** @dev See {IPatchworkProtocol-claimScope} diff --git a/test/Fees.t.sol b/test/Fees.t.sol index 64605c8..6a3ac07 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -49,7 +49,7 @@ contract FeesTest is Test { } function testProtocolBankers() public { - vm.expectRevert("Ownable: caller is not the owner"); // caller is not owner + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, _defaultUser)); _prot.addProtocolBanker(_defaultUser); vm.prank(_patchworkOwner); _prot.addProtocolBanker(_user2Address); @@ -132,7 +132,7 @@ contract FeesTest is Test { vm.prank(_user2Address); _prot.withdrawFromProtocol(50000000); // Remove a banker - vm.expectRevert("Ownable: caller is not the owner"); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, _defaultUser)); _prot.removeProtocolBanker(_user2Address); vm.prank(_patchworkOwner); _prot.removeProtocolBanker(_user2Address); diff --git a/test/PatchworkNFT.t.sol b/test/PatchworkNFT.t.sol index 5d31825..fce9d28 100644 --- a/test/PatchworkNFT.t.sol +++ b/test/PatchworkNFT.t.sol @@ -86,13 +86,13 @@ contract PatchworkNFTTest is Test { // test wrong user revert vm.startPrank(_userAddress); assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); - vm.expectRevert("ERC721: caller is not token owner or approved"); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721InsufficientApproval.selector, _userAddress, n)); _testPatchworkNFT.transferFrom(_user2Address, _userAddress, n); assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); - vm.expectRevert("ERC721: caller is not token owner or approved"); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721InsufficientApproval.selector, _userAddress, n)); _testPatchworkNFT.safeTransferFrom(_user2Address, _userAddress, n); assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); - vm.expectRevert("ERC721: caller is not token owner or approved"); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721InsufficientApproval.selector, _userAddress, n)); _testPatchworkNFT.safeTransferFrom(_user2Address, _userAddress, n, bytes("abcd")); assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); } @@ -171,13 +171,13 @@ contract PatchworkNFTTest is Test { // test wrong user revert vm.startPrank(_userAddress); assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); - vm.expectRevert("ERC721: caller is not token owner or approved"); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721InsufficientApproval.selector, _userAddress, n)); _testPatchworkNFT.transferFromWithFreezeNonce(_user2Address, _userAddress, n, 1); assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); - vm.expectRevert("ERC721: caller is not token owner or approved"); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721InsufficientApproval.selector, _userAddress, n)); _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, n, 1); assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); - vm.expectRevert("ERC721: caller is not token owner or approved"); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721InsufficientApproval.selector, _userAddress, n)); _testPatchworkNFT.safeTransferFromWithFreezeNonce(_user2Address, _userAddress, n, bytes("abcd"), 1); assertEq(_user2Address, _testPatchworkNFT.ownerOf(n)); vm.stopPrank(); diff --git a/test/nfts/Test1155PatchNFT.sol b/test/nfts/Test1155PatchNFT.sol index 8477070..228d10b 100644 --- a/test/nfts/Test1155PatchNFT.sol +++ b/test/nfts/Test1155PatchNFT.sol @@ -42,7 +42,7 @@ contract Test1155PatchNFT is Patchwork1155Patch { } function burn(uint256 tokenId) external { - _burn(tokenId); + _burnPatch(tokenId); } } @@ -86,7 +86,7 @@ contract TestReversible1155PatchNFT is PatchworkReversible1155Patch { } function burn(uint256 tokenId) external { - _burn(tokenId); + _burnPatch(tokenId); } } \ No newline at end of file diff --git a/test/nfts/TestAccountPatchNFT.sol b/test/nfts/TestAccountPatchNFT.sol index 7e1cc86..6e8ffd1 100644 --- a/test/nfts/TestAccountPatchNFT.sol +++ b/test/nfts/TestAccountPatchNFT.sol @@ -49,21 +49,32 @@ contract TestAccountPatchNFT is PatchworkReversibleAccountPatch { function burn(uint256 tokenId) public { // test only - _burn(tokenId); + _burnPatch(tokenId); } - function _beforeTokenTransfer( - address from, - address to, - uint256 firstTokenId, - uint256 /*batchSize*/ - ) internal override view{ - if (_sameOwnerModel) { - // allow burn only + /** + @dev See {IERC721-transferFrom}. + */ + function transferFrom(address from, address to, uint256 tokenId) public virtual override { + _checkTransfer(from, to, tokenId); + super.transferFrom(from, to, tokenId); + } + + /** + @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override { + _checkTransfer(from, to, tokenId); + super.safeTransferFrom(from, to, tokenId, data); + } + + function _checkTransfer(address from, address to, uint256 tokenId) internal { + if (_sameOwnerModel) { + // allow burn only if (from == address(0)) { // mint allowed } else if (to != address(0)) { - revert IPatchworkProtocol.TransferNotAllowed(address(this), firstTokenId); + revert IPatchworkProtocol.TransferNotAllowed(address(this), tokenId); } } } diff --git a/test/nfts/TestPatchFragmentNFT.sol b/test/nfts/TestPatchFragmentNFT.sol index 8aa88d2..06d53f8 100644 --- a/test/nfts/TestPatchFragmentNFT.sol +++ b/test/nfts/TestPatchFragmentNFT.sol @@ -127,10 +127,6 @@ contract TestPatchFragmentNFT is PatchworkReversiblePatch, PatchworkFragmentSing function burn(uint256 tokenId) public { // test only - _burn(tokenId); - } - - function _burn(uint256 tokenId) internal virtual override(PatchworkReversiblePatch, ERC721) { - return PatchworkReversiblePatch._burn(tokenId); + _burnPatch(tokenId); } } \ No newline at end of file diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index c440346..50e2395 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -257,6 +257,6 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { function burn(uint256 tokenId) public { // test only - _burn(tokenId); + _burnPatch(tokenId); } } \ No newline at end of file diff --git a/test/nfts/TestPatchNFT.sol b/test/nfts/TestPatchNFT.sol index 7cf8a1c..902120c 100644 --- a/test/nfts/TestPatchNFT.sol +++ b/test/nfts/TestPatchNFT.sol @@ -97,6 +97,6 @@ contract TestPatchNFT is PatchworkPatch { function burn(uint256 tokenId) public { // test only - protocol does not currently support this as you can't mint another patch later - _burn(tokenId); + _burnPatch(tokenId); } } \ No newline at end of file From 214593f621d95c33a2b8efa9b2db5fc0e4dcb810 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Wed, 17 Jan 2024 14:22:40 -0800 Subject: [PATCH 56/63] Create2 support (#81) * Update to latest forge-std * Moved implicit owner out to support create2 on PP and 721 --- lib/forge-std | 2 +- src/Patchwork721.sol | 6 ++++-- src/PatchworkProtocol.sol | 17 +++++++++-------- test/Fees.t.sol | 2 +- test/Patchwork1155Patch.t.sol | 2 +- test/PatchworkAccountPatch.t.sol | 2 +- test/PatchworkFragmentMulti.t.sol | 2 +- test/PatchworkFragmentSingle.t.sol | 2 +- test/PatchworkNFT.t.sol | 2 +- test/PatchworkNFTReferences.t.sol | 2 +- test/PatchworkPatch.t.sol | 2 +- test/PatchworkProtocol.t.sol | 2 +- test/TestDynamicArrayLiteRefNFT.t.sol | 2 +- test/nfts/Test1155PatchNFT.sol | 4 ++-- test/nfts/TestAccountPatchNFT.sol | 4 ++-- test/nfts/TestDynamicArrayLiteRefNFT.sol | 2 +- test/nfts/TestFragmentLiteRefNFT.sol | 2 +- test/nfts/TestFragmentSingleNFT.sol | 2 +- test/nfts/TestMultiFragmentNFT.sol | 2 +- test/nfts/TestPatchFragmentNFT.sol | 2 +- test/nfts/TestPatchLiteRefNFT.sol | 2 +- test/nfts/TestPatchNFT.sol | 2 +- test/nfts/TestPatchworkNFT.sol | 2 +- 23 files changed, 36 insertions(+), 33 deletions(-) diff --git a/lib/forge-std b/lib/forge-std index 2b58ecb..36c303b 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 2b58ecbcf3dfde7a75959dc7b4eb3d0670278de6 +Subproject commit 36c303b7ffdd842d06b1ec2744c9b9b5fb3083f3 diff --git a/src/Patchwork721.sol b/src/Patchwork721.sol index d718e32..4423826 100644 --- a/src/Patchwork721.sol +++ b/src/Patchwork721.sol @@ -42,13 +42,15 @@ abstract contract Patchwork721 is ERC721, IPatchwork721, IERC4906, Ownable { @param name_ The ERC-721 name. @param symbol_ The ERC-721 symbol. @param manager_ The address that will be set as the manager (PatchworkProtocol). + @param owner_ The address that will be set as the owner */ constructor( string memory scopeName_, string memory name_, string memory symbol_, - address manager_ - ) ERC721(name_, symbol_) Ownable(msg.sender) { + address manager_, + address owner_ + ) ERC721(name_, symbol_) Ownable(owner_) { _scopeName = scopeName_; _manager = manager_; } diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index f0f8c03..d9eec53 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -76,13 +76,14 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { uint256 public constant FEE_CHANGE_TIMELOCK = 1209600; /// The denominator for fee basis points - uint256 private constant FEE_BASIS_DENOM = 10000; + uint256 private constant _FEE_BASIS_DENOM = 10000; /// The maximum basis points patchwork can ever be configured to - uint256 private constant PROTOCOL_FEE_CEILING = 3000; + uint256 private constant _PROTOCOL_FEE_CEILING = 3000; /// Constructor - constructor() Ownable(msg.sender) ReentrancyGuard() {} + /// @param owner_ The address of the initial owner + constructor(address owner_) Ownable(owner_) ReentrancyGuard() {} /** @dev See {IPatchworkProtocol-claimScope} @@ -363,7 +364,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { mintBp = _protocolFeeConfig.mintBp; } - uint256 protocolFee = msg.value * mintBp / FEE_BASIS_DENOM; + uint256 protocolFee = msg.value * mintBp / _FEE_BASIS_DENOM; _protocolBalance += protocolFee; scope.balance += msg.value - protocolFee; } @@ -373,7 +374,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-proposeProtocolFeeConfig} */ function proposeProtocolFeeConfig(FeeConfig memory config) public onlyProtoOwnerBanker { - if (config.assignBp > PROTOCOL_FEE_CEILING || config.mintBp > PROTOCOL_FEE_CEILING || config.patchBp > PROTOCOL_FEE_CEILING) { + if (config.assignBp > _PROTOCOL_FEE_CEILING || config.mintBp > _PROTOCOL_FEE_CEILING || config.patchBp > _PROTOCOL_FEE_CEILING) { revert InvalidFeeValue(); } _proposedFeeConfigs[""] = ProposedFeeConfig(config, block.timestamp, true); @@ -400,7 +401,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { @dev See {IPatchworkProtocol-proposeScopeFeeOverride} */ function proposeScopeFeeOverride(string memory scopeName, FeeConfigOverride memory config) public onlyProtoOwnerBanker { - if (config.assignBp > PROTOCOL_FEE_CEILING || config.mintBp > PROTOCOL_FEE_CEILING || config.patchBp > PROTOCOL_FEE_CEILING) { + if (config.assignBp > _PROTOCOL_FEE_CEILING || config.mintBp > _PROTOCOL_FEE_CEILING || config.patchBp > _PROTOCOL_FEE_CEILING) { revert InvalidFeeValue(); } _proposedFeeConfigs[scopeName] = ProposedFeeConfig( @@ -631,7 +632,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { patchBp = _protocolFeeConfig.patchBp; } - uint256 protocolFee = msg.value * patchBp / FEE_BASIS_DENOM; + uint256 protocolFee = msg.value * patchBp / _FEE_BASIS_DENOM; _protocolBalance += protocolFee; scope.balance += msg.value - protocolFee; } @@ -651,7 +652,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { assignBp = _protocolFeeConfig.assignBp; } - uint256 protocolFee = msg.value * assignBp / FEE_BASIS_DENOM; + uint256 protocolFee = msg.value * assignBp / _FEE_BASIS_DENOM; _protocolBalance += protocolFee; scope.balance += msg.value - protocolFee; } diff --git a/test/Fees.t.sol b/test/Fees.t.sol index 6a3ac07..f892e16 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -33,7 +33,7 @@ contract FeesTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(); + _prot = new PatchworkProtocol(_patchworkOwner); vm.prank(_patchworkOwner); _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(1000, 1000, 1000)); // 10%, 10%, 10% skip(20000000); diff --git a/test/Patchwork1155Patch.t.sol b/test/Patchwork1155Patch.t.sol index d84c278..dedce46 100644 --- a/test/Patchwork1155Patch.t.sol +++ b/test/Patchwork1155Patch.t.sol @@ -27,7 +27,7 @@ contract Patchwork1155PatchTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(); + _prot = new PatchworkProtocol(_patchworkOwner); vm.startPrank(_scopeOwner); _scopeName = "testscope"; diff --git a/test/PatchworkAccountPatch.t.sol b/test/PatchworkAccountPatch.t.sol index 0caad5d..e68dbed 100644 --- a/test/PatchworkAccountPatch.t.sol +++ b/test/PatchworkAccountPatch.t.sol @@ -26,7 +26,7 @@ contract PatchworkAccountPatchTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(); + _prot = new PatchworkProtocol(_patchworkOwner); vm.startPrank(_scopeOwner); _scopeName = "testscope"; diff --git a/test/PatchworkFragmentMulti.t.sol b/test/PatchworkFragmentMulti.t.sol index 761f51b..7b2d075 100644 --- a/test/PatchworkFragmentMulti.t.sol +++ b/test/PatchworkFragmentMulti.t.sol @@ -29,7 +29,7 @@ contract PatchworkFragmentMultiTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(); + _prot = new PatchworkProtocol(_patchworkOwner); _scopeName = "testscope"; vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); diff --git a/test/PatchworkFragmentSingle.t.sol b/test/PatchworkFragmentSingle.t.sol index 799d524..221e9b9 100644 --- a/test/PatchworkFragmentSingle.t.sol +++ b/test/PatchworkFragmentSingle.t.sol @@ -27,7 +27,7 @@ contract PatchworkFragmentSingleTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(); + _prot = new PatchworkProtocol(_patchworkOwner); _scopeName = "testscope"; vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); diff --git a/test/PatchworkNFT.t.sol b/test/PatchworkNFT.t.sol index fce9d28..dddd2bd 100644 --- a/test/PatchworkNFT.t.sol +++ b/test/PatchworkNFT.t.sol @@ -26,7 +26,7 @@ contract PatchworkNFTTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(); + _prot = new PatchworkProtocol(_patchworkOwner); _scopeName = "testscope"; vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); diff --git a/test/PatchworkNFTReferences.t.sol b/test/PatchworkNFTReferences.t.sol index 1d6b2c3..1dd103e 100644 --- a/test/PatchworkNFTReferences.t.sol +++ b/test/PatchworkNFTReferences.t.sol @@ -32,7 +32,7 @@ contract PatchworkNFTCombinedTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(); + _prot = new PatchworkProtocol(_patchworkOwner); _scopeName = "testscope"; vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); diff --git a/test/PatchworkPatch.t.sol b/test/PatchworkPatch.t.sol index 635f4dd..8f33735 100644 --- a/test/PatchworkPatch.t.sol +++ b/test/PatchworkPatch.t.sol @@ -29,7 +29,7 @@ contract PatchworkPatchTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(); + _prot = new PatchworkProtocol(_patchworkOwner); _scopeName = "testscope"; vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index eba958e..c18f2d5 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -37,7 +37,7 @@ contract PatchworkProtocolTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(); + _prot = new PatchworkProtocol(_patchworkOwner); _scopeName = "testscope"; vm.prank(_userAddress); diff --git a/test/TestDynamicArrayLiteRefNFT.t.sol b/test/TestDynamicArrayLiteRefNFT.t.sol index 6a6ce22..a7a2db5 100644 --- a/test/TestDynamicArrayLiteRefNFT.t.sol +++ b/test/TestDynamicArrayLiteRefNFT.t.sol @@ -26,7 +26,7 @@ contract PatchworkAccountPatchTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(); + _prot = new PatchworkProtocol(_patchworkOwner); vm.startPrank(_scopeOwner); _scopeName = "testscope"; diff --git a/test/nfts/Test1155PatchNFT.sol b/test/nfts/Test1155PatchNFT.sol index 228d10b..5350a70 100644 --- a/test/nfts/Test1155PatchNFT.sol +++ b/test/nfts/Test1155PatchNFT.sol @@ -11,7 +11,7 @@ contract Test1155PatchNFT is Patchwork1155Patch { uint256 thing; } - constructor(address manager_) Patchwork721("testscope", "Test1155PatchNFT", "TPLR", manager_) { + constructor(address manager_) Patchwork721("testscope", "Test1155PatchNFT", "TPLR", manager_, msg.sender) { } function schemaURI() pure external returns (string memory) { @@ -55,7 +55,7 @@ contract TestReversible1155PatchNFT is PatchworkReversible1155Patch { uint256 thing; } - constructor(address manager_) Patchwork721("testscope", "Test1155PatchNFT", "TPLR", manager_) { + constructor(address manager_) Patchwork721("testscope", "Test1155PatchNFT", "TPLR", manager_, msg.sender) { } function schemaURI() pure external returns (string memory) { diff --git a/test/nfts/TestAccountPatchNFT.sol b/test/nfts/TestAccountPatchNFT.sol index 6e8ffd1..6200bba 100644 --- a/test/nfts/TestAccountPatchNFT.sol +++ b/test/nfts/TestAccountPatchNFT.sol @@ -12,7 +12,7 @@ contract TestAccountPatchNFT is PatchworkReversibleAccountPatch { uint256 thing; } - constructor(address manager_, bool sameOwnerModel_) Patchwork721("testscope", "TestAccountPatchNFT", "TPLR", manager_) { + constructor(address manager_, bool sameOwnerModel_) Patchwork721("testscope", "TestAccountPatchNFT", "TPLR", manager_, msg.sender) { _sameOwnerModel = sameOwnerModel_; } @@ -68,7 +68,7 @@ contract TestAccountPatchNFT is PatchworkReversibleAccountPatch { super.safeTransferFrom(from, to, tokenId, data); } - function _checkTransfer(address from, address to, uint256 tokenId) internal { + function _checkTransfer(address from, address to, uint256 tokenId) internal view { if (_sameOwnerModel) { // allow burn only if (from == address(0)) { diff --git a/test/nfts/TestDynamicArrayLiteRefNFT.sol b/test/nfts/TestDynamicArrayLiteRefNFT.sol index 20d1612..4177c00 100644 --- a/test/nfts/TestDynamicArrayLiteRefNFT.sol +++ b/test/nfts/TestDynamicArrayLiteRefNFT.sol @@ -35,7 +35,7 @@ contract TestDynamicArrayLiteRefNFT is Patchwork721, PatchworkLiteRef, IPatchwor mapping(uint256 => DynamicLiteRefs) internal _dynamicLiterefStorage; // tokenId => indexed slots - constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", manager_) PatchworkLiteRef() { + constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", manager_, msg.sender) PatchworkLiteRef() { } // ERC-165 diff --git a/test/nfts/TestFragmentLiteRefNFT.sol b/test/nfts/TestFragmentLiteRefNFT.sol index 32a6056..877fe8c 100644 --- a/test/nfts/TestFragmentLiteRefNFT.sol +++ b/test/nfts/TestFragmentLiteRefNFT.sol @@ -36,7 +36,7 @@ contract TestFragmentLiteRefNFT is PatchworkFragmentSingle, PatchworkLiteRef, IP bool _getAssignedToOverrideSet; address _getAssignedToOverride; - constructor (address _manager) Patchwork721("testscope", "TestFragmentLiteRef", "TFLR", _manager) { + constructor (address _manager) Patchwork721("testscope", "TestFragmentLiteRef", "TFLR", _manager, msg.sender) { } // ERC-165 diff --git a/test/nfts/TestFragmentSingleNFT.sol b/test/nfts/TestFragmentSingleNFT.sol index 810e5f8..9c7440d 100644 --- a/test/nfts/TestFragmentSingleNFT.sol +++ b/test/nfts/TestFragmentSingleNFT.sol @@ -26,7 +26,7 @@ contract TestFragmentSingleNFT is PatchworkFragmentSingle { uint256 _nextTokenId; - constructor(address manager_) Patchwork721("testscope", "TestPatchFragment", "TPLR", manager_) PatchworkFragmentSingle() { + constructor(address manager_) Patchwork721("testscope", "TestPatchFragment", "TPLR", manager_, msg.sender) PatchworkFragmentSingle() { } function schemaURI() pure external override returns (string memory) { diff --git a/test/nfts/TestMultiFragmentNFT.sol b/test/nfts/TestMultiFragmentNFT.sol index 5c5e212..5c126eb 100644 --- a/test/nfts/TestMultiFragmentNFT.sol +++ b/test/nfts/TestMultiFragmentNFT.sol @@ -12,7 +12,7 @@ struct TestMultiFragmentNFTMetadata { contract TestMultiFragmentNFT is PatchworkFragmentMulti, IPatchworkMintable { uint256 _nextTokenId; - constructor (address _manager) Patchwork721("testscope", "TestMultiFragmentNFT", "TFLR", _manager) { + constructor (address _manager) Patchwork721("testscope", "TestMultiFragmentNFT", "TFLR", _manager, msg.sender) { } function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { diff --git a/test/nfts/TestPatchFragmentNFT.sol b/test/nfts/TestPatchFragmentNFT.sol index 06d53f8..6bfa371 100644 --- a/test/nfts/TestPatchFragmentNFT.sol +++ b/test/nfts/TestPatchFragmentNFT.sol @@ -26,7 +26,7 @@ contract TestPatchFragmentNFT is PatchworkReversiblePatch, PatchworkFragmentSing uint256 _nextTokenId; - constructor(address manager_) Patchwork721("testscope", "TestPatchFragment", "TPLR", manager_) PatchworkFragmentSingle() { + constructor(address manager_) Patchwork721("testscope", "TestPatchFragment", "TPLR", manager_, msg.sender) PatchworkFragmentSingle() { } // ERC-165 diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index 50e2395..3fcb8b2 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -27,7 +27,7 @@ contract TestPatchLiteRefNFT is PatchworkPatch, PatchworkLiteRef { uint256 _nextTokenId; - constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", manager_) PatchworkLiteRef() { + constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", manager_, msg.sender) PatchworkLiteRef() { } // ERC-165 diff --git a/test/nfts/TestPatchNFT.sol b/test/nfts/TestPatchNFT.sol index 902120c..aa83085 100644 --- a/test/nfts/TestPatchNFT.sol +++ b/test/nfts/TestPatchNFT.sol @@ -25,7 +25,7 @@ contract TestPatchNFT is PatchworkPatch { uint256 _nextTokenId; - constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", manager_) { + constructor(address manager_) Patchwork721("testscope", "TestPatchLiteRef", "TPLR", manager_, msg.sender) { } function schemaURI() pure external override returns (string memory) { diff --git a/test/nfts/TestPatchworkNFT.sol b/test/nfts/TestPatchworkNFT.sol index a426ff7..8584023 100644 --- a/test/nfts/TestPatchworkNFT.sol +++ b/test/nfts/TestPatchworkNFT.sol @@ -12,7 +12,7 @@ contract TestPatchworkNFT is Patchwork721, IPatchworkMintable { uint256 thing; } - constructor(address manager_) Patchwork721("testscope", "TestPatchworkNFT", "TPLR", manager_) { + constructor(address manager_) Patchwork721("testscope", "TestPatchworkNFT", "TPLR", manager_, msg.sender) { } function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) { From 06c25cf89de48755051b62c98ff376447c1c23fc Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 19 Jan 2024 12:37:29 -0800 Subject: [PATCH 57/63] Event fees (#82) * Added mint fees to mint events * all fees in and doAssign restructuring' --- src/PatchworkProtocol.sol | 132 ++++++++++++++------------ src/interfaces/IPatchworkProtocol.sol | 24 +++-- 2 files changed, 89 insertions(+), 67 deletions(-) diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index d9eec53..925864c 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -320,9 +320,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { if (msg.value != config.flatFee) { revert IncorrectFeeAmount(); } - _handleMintFee(scopeName, scope); + (uint256 scopeFee, uint256 protocolFee) = _handleMintFee(scopeName, scope); tokenId = IPatchworkMintable(mintable).mint(to, data); - emit Mint(msg.sender, scopeName, to, mintable, data); + emit Mint(msg.sender, scopeName, to, mintable, data, scopeFee, protocolFee); } /** @@ -334,9 +334,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { if (msg.value != totalFee) { revert IncorrectFeeAmount(); } - _handleMintFee(scopeName, scope); + (uint256 scopeFee, uint256 protocolFee) = _handleMintFee(scopeName, scope); tokenIds = IPatchworkMintable(mintable).mintBatch(to, data, quantity); - emit MintBatch(msg.sender, scopeName, to, mintable, data, quantity); + emit MintBatch(msg.sender, scopeName, to, mintable, data, quantity, scopeFee, protocolFee); } /// Common to mints @@ -354,7 +354,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } /// Common to mints - function _handleMintFee(string memory scopeName, Scope storage scope) internal { + function _handleMintFee(string memory scopeName, Scope storage scope) internal returns (uint256 scopeFee, uint256 protocolFee) { // Account for 100% of the message value if (msg.value != 0) { uint256 mintBp; @@ -364,9 +364,10 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { mintBp = _protocolFeeConfig.mintBp; } - uint256 protocolFee = msg.value * mintBp / _FEE_BASIS_DENOM; + protocolFee = msg.value * mintBp / _FEE_BASIS_DENOM; + scopeFee = msg.value - protocolFee; _protocolBalance += protocolFee; - scope.balance += msg.value - protocolFee; + scope.balance += scopeFee; } } @@ -522,7 +523,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { revert NotAuthorized(msg.sender); } - _handlePatchFee(scopeName, scope, patchAddress); + (uint256 scopeFee, uint256 protocolFee) = _handlePatchFee(scopeName, scope, patchAddress); // limit this to one unique patch (originalAddress+TokenID+patchAddress) bytes32 _hash = keccak256(abi.encodePacked(originalAddress, originalTokenId, patchAddress)); if (_uniquePatches[_hash]) { @@ -530,7 +531,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } _uniquePatches[_hash] = true; tokenId = patch_.mintPatch(owner, IPatchworkPatch.PatchTarget(originalAddress, originalTokenId)); - emit Patch(owner, originalAddress, originalTokenId, patchAddress, tokenId); + emit Patch(owner, originalAddress, originalTokenId, patchAddress, tokenId, scopeFee, protocolFee); return tokenId; } @@ -560,7 +561,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { revert NotAuthorized(msg.sender); } - _handlePatchFee(scopeName, scope, patchAddress); + (uint256 scopeFee, uint256 protocolFee) = _handlePatchFee(scopeName, scope, patchAddress); // limit this to one unique patch (originalAddress+TokenID+patchAddress) bytes32 _hash = keccak256(abi.encodePacked(originalAddress, originalTokenId, originalAccount, patchAddress)); if (_uniquePatches[_hash]) { @@ -568,7 +569,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } _uniquePatches[_hash] = true; tokenId = patch_.mintPatch(to, IPatchwork1155Patch.PatchTarget(originalAddress, originalTokenId, originalAccount)); - emit ERC1155Patch(to, originalAddress, originalTokenId, originalAccount, patchAddress, tokenId); + emit ERC1155Patch(to, originalAddress, originalTokenId, originalAccount, patchAddress, tokenId, scopeFee, protocolFee); return tokenId; } @@ -598,7 +599,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { revert NotAuthorized(msg.sender); } - _handlePatchFee(scopeName, scope, patchAddress); + (uint256 scopeFee, uint256 protocolFee) = _handlePatchFee(scopeName, scope, patchAddress); // limit this to one unique patch (originalAddress+patchAddress) bytes32 _hash = keccak256(abi.encodePacked(originalAddress, patchAddress)); if (_uniquePatches[_hash]) { @@ -606,7 +607,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } _uniquePatches[_hash] = true; tokenId = patch_.mintPatch(owner, originalAddress); - emit AccountPatch(owner, originalAddress, patchAddress, tokenId); + emit AccountPatch(owner, originalAddress, patchAddress, tokenId, scopeFee, protocolFee); return tokenId; } @@ -619,7 +620,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } /// common to patches - function _handlePatchFee(string memory scopeName, Scope storage scope, address patchAddress) private { + function _handlePatchFee(string memory scopeName, Scope storage scope, address patchAddress) private returns (uint256 scopeFee, uint256 protocolFee) { uint256 patchFee = scope.patchFees[patchAddress]; if (msg.value != patchFee) { revert IncorrectFeeAmount(); @@ -632,14 +633,15 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { patchBp = _protocolFeeConfig.patchBp; } - uint256 protocolFee = msg.value * patchBp / _FEE_BASIS_DENOM; + protocolFee = msg.value * patchBp / _FEE_BASIS_DENOM; + scopeFee = msg.value - protocolFee; _protocolBalance += protocolFee; - scope.balance += msg.value - protocolFee; + scope.balance += scopeFee; } } // common to assigns - function _handleAssignFee(string memory scopeName, Scope storage scope, address fragmentAddress) private { + function _handleAssignFee(string memory scopeName, Scope storage scope, address fragmentAddress) private returns (uint256 scopeFee, uint256 protocolFee) { uint256 assignFee = scope.assignFees[fragmentAddress]; if (msg.value != assignFee) { revert IncorrectFeeAmount(); @@ -652,9 +654,10 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } else { assignBp = _protocolFeeConfig.assignBp; } - uint256 protocolFee = msg.value * assignBp / _FEE_BASIS_DENOM; + protocolFee = msg.value * assignBp / _FEE_BASIS_DENOM; + scopeFee = msg.value - protocolFee; _protocolBalance += protocolFee; - scope.balance += msg.value - protocolFee; + scope.balance += scopeFee; } } @@ -722,56 +725,63 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { revert SelfAssignmentNotAllowed(fragment, fragmentTokenId); } IPatchworkAssignable assignable = IPatchworkAssignable(fragment); - if (_isLocked(fragment, fragmentTokenId)) { - revert Locked(fragment, fragmentTokenId); - } // Use the target's scope for general permission and check the fragment for detailed permissions - string memory targetScopeName = _getScopeName(target); - Scope storage targetScope = _mustHaveScope(targetScopeName); - _mustBeWhitelisted(targetScopeName, targetScope, target); { + string memory targetScopeName = _getScopeName(target); + if (!assignable.allowAssignment(fragmentTokenId, target, targetTokenId, targetOwner, msg.sender, targetScopeName)) { + revert NotAuthorized(msg.sender); + } + Scope storage targetScope = _mustHaveScope(targetScopeName); + _mustBeWhitelisted(targetScopeName, targetScope, target); + if (targetScope.owner == msg.sender || targetScope.operators[msg.sender]) { + // all good + } else if (targetScope.allowUserAssign) { + // msg.sender must own the target + if (targetOwner != msg.sender) { + revert NotAuthorized(msg.sender); + } + } else { + revert NotAuthorized(msg.sender); + } + } + uint256 scopeFee = 0; + uint256 protocolFee = 0; + // Check fragment whitelisting and handle fees + { + if (_isLocked(fragment, fragmentTokenId)) { + revert Locked(fragment, fragmentTokenId); + } // Whitelist check, these variables do not need to stay in the function level stack string memory fragmentScopeName = _getScopeName(fragment); Scope storage fragmentScope = _mustHaveScope(fragmentScopeName); _mustBeWhitelisted(fragmentScopeName, fragmentScope, fragment); - _handleAssignFee(fragmentScopeName, fragmentScope, fragment); + (scopeFee, protocolFee) = _handleAssignFee(fragmentScopeName, fragmentScope, fragment); } - if (targetScope.owner == msg.sender || targetScope.operators[msg.sender]) { - // all good - } else if (targetScope.allowUserAssign) { - // msg.sender must own the target - if (targetOwner != msg.sender) { - revert NotAuthorized(msg.sender); + uint64 ref; + // Handle storage and duplicate checks + { + bool redacted; + (ref, redacted) = IPatchworkLiteRef(target).getLiteReference(fragment, fragmentTokenId); + if (redacted) { + revert FragmentRedacted(address(fragment)); } - } else { - revert NotAuthorized(msg.sender); - } - if (!IPatchworkAssignable(fragment).allowAssignment(fragmentTokenId, target, targetTokenId, targetOwner, msg.sender, targetScopeName)) { - revert NotAuthorized(msg.sender); - } - bytes32 targetRef; - // reduce stack to stay under limit - address _target = target; - uint256 _targetTokenId = targetTokenId; - address _fragment = fragment; - uint256 _fragmentTokenId = fragmentTokenId; - (uint64 ref, bool redacted) = IPatchworkLiteRef(_target).getLiteReference(_fragment, _fragmentTokenId); - // targetRef is a compound key (targetAddr+targetTokenID+fragmentAddr+fragmentTokenID) - blocks duplicate assignments - targetRef = keccak256(abi.encodePacked(_target, _targetTokenId, ref)); - if (ref == 0) { - revert FragmentUnregistered(address(_fragment)); - } - if (redacted) { - revert FragmentRedacted(address(_fragment)); - } - if (_liteRefs[targetRef]) { - revert FragmentAlreadyAssigned(address(_fragment), _fragmentTokenId); - } - // call assign on the fragment - assignable.assign(_fragmentTokenId, _target, _targetTokenId); - // add to our storage of assignments - _liteRefs[targetRef] = true; - emit Assign(targetOwner, _fragment, _fragmentTokenId, _target, _targetTokenId); + if (ref == 0) { + revert FragmentUnregistered(address(fragment)); + } + // targetRef is a compound key (targetAddr+targetTokenID+ref) - blocks duplicate assignments + bytes32 targetRef = keccak256(abi.encodePacked(target, targetTokenId, ref)); + if (_liteRefs[targetRef]) { + revert FragmentAlreadyAssigned(address(fragment), fragmentTokenId); + } + // add to our storage of assignments + _liteRefs[targetRef] = true; + // call assign on the fragment + assignable.assign(fragmentTokenId, target, targetTokenId); + } + // these two end up beyond stack depth on some compiler settings. + address fragment_ = fragment; + uint256 fragmentTokenId_ = fragmentTokenId; + emit Assign(targetOwner, fragment_, fragmentTokenId_, target, targetTokenId, scopeFee, protocolFee); return ref; } diff --git a/src/interfaces/IPatchworkProtocol.sol b/src/interfaces/IPatchworkProtocol.sol index a9ed2d4..36ec111 100644 --- a/src/interfaces/IPatchworkProtocol.sol +++ b/src/interfaces/IPatchworkProtocol.sol @@ -375,8 +375,10 @@ interface IPatchworkProtocol { @param fragmentTokenId The tokenId of the fragment @param targetAddress The address of the target's contract @param targetTokenId The tokenId of the target + @param scopeFee The fee collected to the scope + @param protocolFee The fee collected to the protocol */ - event Assign(address indexed owner, address fragmentAddress, uint256 fragmentTokenId, address indexed targetAddress, uint256 indexed targetTokenId); + event Assign(address indexed owner, address fragmentAddress, uint256 fragmentTokenId, address indexed targetAddress, uint256 indexed targetTokenId, uint256 scopeFee, uint256 protocolFee); /** @notice Emitted when a fragment is unassigned @@ -395,8 +397,10 @@ interface IPatchworkProtocol { @param originalTokenId The tokenId of the original 721 @param patchAddress The address of the patch's contract @param patchTokenId The tokenId of the patch + @param scopeFee The fee collected to the scope + @param protocolFee The fee collected to the protocol */ - event Patch(address indexed owner, address originalAddress, uint256 originalTokenId, address indexed patchAddress, uint256 indexed patchTokenId); + event Patch(address indexed owner, address originalAddress, uint256 originalTokenId, address indexed patchAddress, uint256 indexed patchTokenId, uint256 scopeFee, uint256 protocolFee); /** @notice Emitted when a patch is minted @@ -406,8 +410,10 @@ interface IPatchworkProtocol { @param originalAccount The address of the original 1155's account @param patchAddress The address of the patch's contract @param patchTokenId The tokenId of the patch + @param scopeFee The fee collected to the scope + @param protocolFee The fee collected to the protocol */ - event ERC1155Patch(address indexed owner, address originalAddress, uint256 originalTokenId, address originalAccount, address indexed patchAddress, uint256 indexed patchTokenId); + event ERC1155Patch(address indexed owner, address originalAddress, uint256 originalTokenId, address originalAccount, address indexed patchAddress, uint256 indexed patchTokenId, uint256 scopeFee, uint256 protocolFee); /** @@ -416,8 +422,10 @@ interface IPatchworkProtocol { @param originalAddress The address of the original account @param patchAddress The address of the patch's contract @param patchTokenId The tokenId of the patch + @param scopeFee The fee collected to the scope + @param protocolFee The fee collected to the protocol */ - event AccountPatch(address indexed owner, address originalAddress, address indexed patchAddress, uint256 indexed patchTokenId); + event AccountPatch(address indexed owner, address originalAddress, address indexed patchAddress, uint256 indexed patchTokenId, uint256 scopeFee, uint256 protocolFee); /** @notice Emitted when a new scope is claimed @@ -552,8 +560,10 @@ interface IPatchworkProtocol { @param to The receipient of the mint @param mintable The IPatchworkMintable minted @param data The data used to mint + @param scopeFee The fee collected to the scope + @param protocolFee The fee collected to the protocol */ - event Mint(address indexed actor, string scopeName, address indexed to, address indexed mintable, bytes data); + event Mint(address indexed actor, string scopeName, address indexed to, address indexed mintable, bytes data, uint256 scopeFee, uint256 protocolFee); /** @notice Emitted on batch mint @@ -563,8 +573,10 @@ interface IPatchworkProtocol { @param mintable The IPatchworkMintable minted @param data The data used to mint @param quantity The quantity minted + @param scopeFee The fee collected to the scope + @param protocolFee The fee collected to the protocol */ - event MintBatch(address indexed actor, string scopeName, address indexed to, address indexed mintable, bytes data, uint256 quantity); + event MintBatch(address indexed actor, string scopeName, address indexed to, address indexed mintable, bytes data, uint256 quantity, uint256 scopeFee, uint256 protocolFee); /** @notice Emitted on protocol fee config proposed From 3aecbddd1939f746cb3f11f009d22c1715bf6377 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Sun, 21 Jan 2024 10:52:10 -0800 Subject: [PATCH 58/63] Refactored doAssign for readability and object bytesize optimization (#83) --- src/PatchworkProtocol.sol | 118 +++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 54 deletions(-) diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 925864c..6ce3fc5 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -724,67 +724,77 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { if (fragment == target && fragmentTokenId == targetTokenId) { revert SelfAssignmentNotAllowed(fragment, fragmentTokenId); } - IPatchworkAssignable assignable = IPatchworkAssignable(fragment); // Use the target's scope for general permission and check the fragment for detailed permissions - { - string memory targetScopeName = _getScopeName(target); - if (!assignable.allowAssignment(fragmentTokenId, target, targetTokenId, targetOwner, msg.sender, targetScopeName)) { - revert NotAuthorized(msg.sender); - } - Scope storage targetScope = _mustHaveScope(targetScopeName); - _mustBeWhitelisted(targetScopeName, targetScope, target); - if (targetScope.owner == msg.sender || targetScope.operators[msg.sender]) { - // all good - } else if (targetScope.allowUserAssign) { - // msg.sender must own the target - if (targetOwner != msg.sender) { - revert NotAuthorized(msg.sender); - } - } else { - revert NotAuthorized(msg.sender); - } + (uint256 scopeFee, uint256 protocolFee) = _doAssignPermissionsAndFees(fragment, fragmentTokenId, target, targetTokenId, targetOwner); + // Handle storage and duplicate checks + uint64 ref = _doAssignStorageAndDupes(fragment, fragmentTokenId, target, targetTokenId); + // these two end up beyond stack depth on some compiler settings. + emit Assign(targetOwner, fragment, fragmentTokenId, target, targetTokenId, scopeFee, protocolFee); + return ref; + } + + /** + @notice Handles assignment permissions and fees + @param fragment the IPatchworkAssignable's address + @param fragmentTokenId the IPatchworkAssignable's tokenId + @param target the IPatchworkLiteRef target's address + @param targetTokenId the IPatchworkLiteRef target's tokenId + @param targetOwner the owner address of the target + */ + function _doAssignPermissionsAndFees(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, address targetOwner) private returns (uint256 scopeFee, uint256 protocolFee) { + string memory targetScopeName = _getScopeName(target); + if (!IPatchworkAssignable(fragment).allowAssignment(fragmentTokenId, target, targetTokenId, targetOwner, msg.sender, targetScopeName)) { + revert NotAuthorized(msg.sender); } - uint256 scopeFee = 0; - uint256 protocolFee = 0; - // Check fragment whitelisting and handle fees - { - if (_isLocked(fragment, fragmentTokenId)) { - revert Locked(fragment, fragmentTokenId); + Scope storage targetScope = _mustHaveScope(targetScopeName); + _mustBeWhitelisted(targetScopeName, targetScope, target); + if (targetScope.owner == msg.sender || targetScope.operators[msg.sender]) { + // all good + } else if (targetScope.allowUserAssign) { + // msg.sender must own the target + if (targetOwner != msg.sender) { + revert NotAuthorized(msg.sender); } - // Whitelist check, these variables do not need to stay in the function level stack - string memory fragmentScopeName = _getScopeName(fragment); - Scope storage fragmentScope = _mustHaveScope(fragmentScopeName); - _mustBeWhitelisted(fragmentScopeName, fragmentScope, fragment); - (scopeFee, protocolFee) = _handleAssignFee(fragmentScopeName, fragmentScope, fragment); + } else { + revert NotAuthorized(msg.sender); } - uint64 ref; - // Handle storage and duplicate checks - { - bool redacted; - (ref, redacted) = IPatchworkLiteRef(target).getLiteReference(fragment, fragmentTokenId); - if (redacted) { - revert FragmentRedacted(address(fragment)); - } - if (ref == 0) { - revert FragmentUnregistered(address(fragment)); - } - // targetRef is a compound key (targetAddr+targetTokenID+ref) - blocks duplicate assignments - bytes32 targetRef = keccak256(abi.encodePacked(target, targetTokenId, ref)); - if (_liteRefs[targetRef]) { - revert FragmentAlreadyAssigned(address(fragment), fragmentTokenId); - } - // add to our storage of assignments - _liteRefs[targetRef] = true; - // call assign on the fragment - assignable.assign(fragmentTokenId, target, targetTokenId); + if (_isLocked(fragment, fragmentTokenId)) { + revert Locked(fragment, fragmentTokenId); } - // these two end up beyond stack depth on some compiler settings. - address fragment_ = fragment; - uint256 fragmentTokenId_ = fragmentTokenId; - emit Assign(targetOwner, fragment_, fragmentTokenId_, target, targetTokenId, scopeFee, protocolFee); - return ref; + // Whitelist check, these variables do not need to stay in the function level stack + string memory fragmentScopeName = _getScopeName(fragment); + Scope storage fragmentScope = _mustHaveScope(fragmentScopeName); + _mustBeWhitelisted(fragmentScopeName, fragmentScope, fragment); + (scopeFee, protocolFee) = _handleAssignFee(fragmentScopeName, fragmentScope, fragment); } + /** + @notice Handles assignment storage and duplicate checks + @param fragment the IPatchworkAssignable's address + @param fragmentTokenId the IPatchworkAssignable's tokenId + @param target the IPatchworkLiteRef target's address + @param targetTokenId the IPatchworkLiteRef target's tokenId + */ + function _doAssignStorageAndDupes(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) private returns (uint64 ref) { + bool redacted; + (ref, redacted) = IPatchworkLiteRef(target).getLiteReference(fragment, fragmentTokenId); + if (redacted) { + revert FragmentRedacted(address(fragment)); + } + if (ref == 0) { + revert FragmentUnregistered(address(fragment)); + } + // targetRef is a compound key (targetAddr+targetTokenID+ref) - blocks duplicate assignments + bytes32 targetRef = keccak256(abi.encodePacked(target, targetTokenId, ref)); + if (_liteRefs[targetRef]) { + revert FragmentAlreadyAssigned(address(fragment), fragmentTokenId); + } + // add to our storage of assignments + _liteRefs[targetRef] = true; + // call assign on the fragment + IPatchworkAssignable(fragment).assign(fragmentTokenId, target, targetTokenId); + } + /** @dev See {IPatchworkProtocol-unassign} */ From c6a2cabdcc1f85644169dc1c7ad39285206c6f1f Mon Sep 17 00:00:00 2001 From: Rob Green Date: Tue, 23 Jan 2024 17:17:07 -0800 Subject: [PATCH 59/63] Fixed recursive burn bug (#85) --- src/Patchwork1155Patch.sol | 2 +- test/PatchworkProtocol.t.sol | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Patchwork1155Patch.sol b/src/Patchwork1155Patch.sol index f32ecba..f4c843a 100644 --- a/src/Patchwork1155Patch.sol +++ b/src/Patchwork1155Patch.sol @@ -84,6 +84,6 @@ abstract contract PatchworkReversible1155Patch is Patchwork1155Patch, IPatchwork function _burnPatch(uint256 tokenId) internal virtual override { PatchTarget storage target = _targetsById[tokenId]; delete _idsByTargetHash[keccak256(abi.encode(target))]; - _burnPatch(tokenId); + super._burnPatch(tokenId); } } \ No newline at end of file diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index c18f2d5..6f6e172 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -57,7 +57,6 @@ contract PatchworkProtocolTest is Test { vm.startPrank(_scopeOwner); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _scopeOwner)); _prot.claimScope(""); - vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); assertEq(_prot.getScopeOwner(_scopeName), _scopeOwner); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.ScopeExists.selector, _scopeName)); From 0de949d12cfbb1191844238e6ef68b4e90e2da19 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 26 Jan 2024 12:20:09 -0800 Subject: [PATCH 60/63] Assigner delegate contract (#86) * First stab at delegate contract * Clean up submodules * Update solidity spec to 0.8.23 * Refactor and natspec * Timelock delegate change code in * Delegate upgrade tests * small fixup --- .gitmodules | 10 +- lib/forge-std | 2 +- lib/openzeppelin-contracts-upgradeable | 1 - lib/openzeppelin-foundry-upgrades | 1 - remappings.txt | 4 +- src/Patchwork1155Patch.sol | 2 +- src/Patchwork721.sol | 2 +- src/PatchworkAccountPatch.sol | 2 +- src/PatchworkFragmentMulti.sol | 2 +- src/PatchworkFragmentSingle.sol | 2 +- src/PatchworkLiteRef.sol | 2 +- src/PatchworkPatch.sol | 2 +- src/PatchworkProtocol.sol | 414 +++--------------- src/PatchworkProtocolAssigner.sol | 374 ++++++++++++++++ src/PatchworkProtocolCommon.sol | 113 +++++ src/PatchworkUtils.sol | 2 +- src/interfaces/IERC4906.sol | 2 +- src/interfaces/IERC5192.sol | 2 +- src/interfaces/IPatchwork1155Patch.sol | 2 +- src/interfaces/IPatchwork721.sol | 2 +- src/interfaces/IPatchworkAccountPatch.sol | 2 +- src/interfaces/IPatchworkAssignable.sol | 2 +- src/interfaces/IPatchworkLiteRef.sol | 2 +- src/interfaces/IPatchworkMintable.sol | 2 +- src/interfaces/IPatchworkMultiAssignable.sol | 2 +- src/interfaces/IPatchworkPatch.sol | 2 +- src/interfaces/IPatchworkProtocol.sol | 61 ++- src/interfaces/IPatchworkScoped.sol | 2 +- src/interfaces/IPatchworkSingleAssignable.sol | 2 +- test/AssignerDelegate.t.sol | 69 +++ test/Fees.t.sol | 10 +- test/Gas.t.sol | 2 +- test/Patchwork1155Patch.t.sol | 5 +- test/PatchworkAccountPatch.t.sol | 5 +- test/PatchworkFragmentMulti.t.sol | 3 +- test/PatchworkFragmentSingle.t.sol | 5 +- test/PatchworkNFT.t.sol | 5 +- test/PatchworkNFTReferences.t.sol | 5 +- test/PatchworkPatch.t.sol | 7 +- test/PatchworkProtocol.t.sol | 5 +- test/PatchworkUtils.t.sol | 2 +- test/TestDynamicArrayLiteRefNFT.t.sol | 5 +- test/TestPatchLiteRefNFT.t.sol | 2 +- test/nfts/Test1155PatchNFT.sol | 2 +- test/nfts/TestAccountPatchNFT.sol | 2 +- test/nfts/TestBase1155.sol | 2 +- test/nfts/TestBaseNFT.sol | 2 +- test/nfts/TestDynamicArrayLiteRefNFT.sol | 2 +- test/nfts/TestFragmentLiteRefNFT.sol | 2 +- test/nfts/TestFragmentSingleNFT.sol | 2 +- test/nfts/TestMultiFragmentNFT.sol | 2 +- test/nfts/TestPatchFragmentNFT.sol | 2 +- test/nfts/TestPatchLiteRefNFT.sol | 2 +- test/nfts/TestPatchNFT.sol | 2 +- test/nfts/TestPatchworkNFT.sol | 2 +- 55 files changed, 740 insertions(+), 434 deletions(-) delete mode 160000 lib/openzeppelin-contracts-upgradeable delete mode 160000 lib/openzeppelin-foundry-upgrades create mode 100644 src/PatchworkProtocolAssigner.sol create mode 100644 src/PatchworkProtocolCommon.sol create mode 100644 test/AssignerDelegate.t.sol diff --git a/.gitmodules b/.gitmodules index bd36fda..b3c9f6e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,14 +1,8 @@ [submodule "lib/openzeppelin-contracts"] - branch = v4.8.2 + branch = v5.0.1 path = lib/openzeppelin-contracts url = https://github.com/openzeppelin/openzeppelin-contracts [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std - branch = v1.5.2 -[submodule "lib/openzeppelin-foundry-upgrades"] - path = lib/openzeppelin-foundry-upgrades - url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades -[submodule "lib/openzeppelin-contracts-upgradeable"] - path = lib/openzeppelin-contracts-upgradeable - url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable + branch = v1.7.6 diff --git a/lib/forge-std b/lib/forge-std index 36c303b..ae570fe 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 36c303b7ffdd842d06b1ec2744c9b9b5fb3083f3 +Subproject commit ae570fec082bfe1c1f45b0acca4a2b4f84d345ce diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable deleted file mode 160000 index fbdb824..0000000 --- a/lib/openzeppelin-contracts-upgradeable +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fbdb824a735891908d5588b28e0da5852d7ed7ba diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades deleted file mode 160000 index ba9b426..0000000 --- a/lib/openzeppelin-foundry-upgrades +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ba9b4269f2d34425dcff4d9d242f398ddcefddef diff --git a/remappings.txt b/remappings.txt index 968141a..fd818cf 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,5 +1,3 @@ -@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/ -@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ -@patchwork/=src/ +@openzeppelin/=lib/openzeppelin-contracts/ forge-std/=lib/forge-std/src/ diff --git a/src/Patchwork1155Patch.sol b/src/Patchwork1155Patch.sol index f4c843a..8c71134 100644 --- a/src/Patchwork1155Patch.sol +++ b/src/Patchwork1155Patch.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "./Patchwork721.sol"; import "./interfaces/IPatchwork1155Patch.sol"; diff --git a/src/Patchwork721.sol b/src/Patchwork721.sol index 4423826..a3b1b2a 100644 --- a/src/Patchwork721.sol +++ b/src/Patchwork721.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; diff --git a/src/PatchworkAccountPatch.sol b/src/PatchworkAccountPatch.sol index 9fc454a..a63a0c9 100644 --- a/src/PatchworkAccountPatch.sol +++ b/src/PatchworkAccountPatch.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "./interfaces/IPatchworkAccountPatch.sol"; import "./interfaces/IPatchworkProtocol.sol"; diff --git a/src/PatchworkFragmentMulti.sol b/src/PatchworkFragmentMulti.sol index 02f0c14..eff9bd6 100644 --- a/src/PatchworkFragmentMulti.sol +++ b/src/PatchworkFragmentMulti.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "./Patchwork721.sol"; import "./interfaces/IPatchworkMultiAssignable.sol"; diff --git a/src/PatchworkFragmentSingle.sol b/src/PatchworkFragmentSingle.sol index d174c4d..b5b6d7a 100644 --- a/src/PatchworkFragmentSingle.sol +++ b/src/PatchworkFragmentSingle.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "./Patchwork721.sol"; import "./interfaces/IPatchworkSingleAssignable.sol"; diff --git a/src/PatchworkLiteRef.sol b/src/PatchworkLiteRef.sol index 9da2bd1..c9d2274 100644 --- a/src/PatchworkLiteRef.sol +++ b/src/PatchworkLiteRef.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import "./interfaces/IPatchworkLiteRef.sol"; diff --git a/src/PatchworkPatch.sol b/src/PatchworkPatch.sol index 7b97bf0..eaf3125 100644 --- a/src/PatchworkPatch.sol +++ b/src/PatchworkPatch.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "./Patchwork721.sol"; import "./interfaces/IPatchworkPatch.sol"; diff --git a/src/PatchworkProtocol.sol b/src/PatchworkProtocol.sol index 6ce3fc5..0c6b833 100644 --- a/src/PatchworkProtocol.sol +++ b/src/PatchworkProtocol.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; /** @@ -16,8 +16,7 @@ pragma solidity ^0.8.13; */ import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "./PatchworkProtocolCommon.sol"; import "./interfaces/IPatchwork721.sol"; import "./interfaces/IPatchworkSingleAssignable.sol"; import "./interfaces/IPatchworkMultiAssignable.sol"; @@ -33,48 +32,14 @@ import "./interfaces/IPatchworkScoped.sol"; @title Patchwork Protocol @author Runic Labs, Inc */ -contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { - - /// Scopes - mapping(string => Scope) private _scopes; - - /** - @notice unique references - @dev A hash of target + targetTokenId + literef provides uniqueness - */ - mapping(bytes32 => bool) private _liteRefs; - - /** - @notice unique patches - @dev Hash of the patch mapped to a boolean indicating its uniqueness - */ - mapping(bytes32 => bool) private _uniquePatches; - - /// Balance of the protocol - uint256 private _protocolBalance; - - /** - @notice protocol bankers - @dev Map of addresses authorized to set fees and withdraw funds for the protocol - @dev Does not allow for scope balance withdrawl - */ - mapping(address => bool) private _protocolBankers; - - /// Current protocol fee configuration - FeeConfig private _protocolFeeConfig; - - /// Proposed protocol fee configuration - mapping(string => ProposedFeeConfig) private _proposedFeeConfigs; - - /// scope-based fee overrides - mapping(string => FeeConfigOverride) private _scopeFeeOverrides; - - /// Scope name cache - mapping(address => string) private _scopeNameCache; - +contract PatchworkProtocol is IPatchworkProtocol, PatchworkProtocolCommon { + /// How much time must elapse before a fee change can be committed (1209600 = 2 weeks) uint256 public constant FEE_CHANGE_TIMELOCK = 1209600; + /// How much time must elapse before a contract upgrade can be committed (1209600 = 2 weeks) + uint256 public constant CONTRACT_UPGRADE_TIMELOCK = 1209600; + /// The denominator for fee basis points uint256 private constant _FEE_BASIS_DENOM = 10000; @@ -83,7 +48,9 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { /// Constructor /// @param owner_ The address of the initial owner - constructor(address owner_) Ownable(owner_) ReentrancyGuard() {} + constructor(address owner_, address assignerDelegate_) PatchworkProtocolCommon(owner_) { + _assignerDelegate = assignerDelegate_; + } /** @dev See {IPatchworkProtocol-claimScope} @@ -640,293 +607,85 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } } - // common to assigns - function _handleAssignFee(string memory scopeName, Scope storage scope, address fragmentAddress) private returns (uint256 scopeFee, uint256 protocolFee) { - uint256 assignFee = scope.assignFees[fragmentAddress]; - if (msg.value != assignFee) { - revert IncorrectFeeAmount(); - } - if (msg.value > 0) { - uint256 assignBp; - FeeConfigOverride storage feeOverride = _scopeFeeOverrides[scopeName]; - if (feeOverride.active) { - assignBp = feeOverride.assignBp; - } else { - assignBp = _protocolFeeConfig.assignBp; + function _delegatecall(address delegate, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returndata) = delegate.delegatecall(data); + if (!success) { + if (returndata.length == 0) revert(); + assembly { + revert(add(32, returndata), mload(returndata)) } - protocolFee = msg.value * assignBp / _FEE_BASIS_DENOM; - scopeFee = msg.value - protocolFee; - _protocolBalance += protocolFee; - scope.balance += scopeFee; } + return returndata; } /** @dev See {IPatchworkProtocol-assign} */ - function assign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public payable mustNotBeFrozen(target, targetTokenId) { - address targetOwner = IERC721(target).ownerOf(targetTokenId); - uint64 ref = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); - IPatchworkLiteRef(target).addReference(targetTokenId, ref); + function assign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public payable { + _delegatecall(_assignerDelegate, abi.encodeWithSignature("assign(address,uint256,address,uint256)", fragment, fragmentTokenId, target, targetTokenId)); } /** @dev See {IPatchworkProtocol-assign} */ - function assign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public payable mustNotBeFrozen(target, targetTokenId) { - address targetOwner = IERC721(target).ownerOf(targetTokenId); - uint64 ref = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); - IPatchworkLiteRef(target).addReference(targetTokenId, ref, targetMetadataId); - } + function assign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public payable { + _delegatecall(_assignerDelegate, abi.encodeWithSignature("assign(address,uint256,address,uint256,uint256)", fragment, fragmentTokenId, target, targetTokenId, targetMetadataId)); + } /** @dev See {IPatchworkProtocol-assignBatch} */ - function assignBatch(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) public payable mustNotBeFrozen(target, targetTokenId) { - (uint64[] memory refs, ) = _batchAssignCommon(fragments, tokenIds, target, targetTokenId); - IPatchworkLiteRef(target).addReferenceBatch(targetTokenId, refs); + function assignBatch(address[] calldata fragments, uint256[] calldata tokenIds, address target, uint256 targetTokenId) public payable { + _delegatecall(_assignerDelegate, abi.encodeWithSignature("assignBatch(address[],uint256[],address,uint256)", fragments, tokenIds, target, targetTokenId)); } /** @dev See {IPatchworkProtocol-assignBatch} */ - function assignBatch(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId, uint256 targetMetadataId) public payable mustNotBeFrozen(target, targetTokenId) { - (uint64[] memory refs, ) = _batchAssignCommon(fragments, tokenIds, target, targetTokenId); - IPatchworkLiteRef(target).addReferenceBatch(targetTokenId, refs, targetMetadataId); - } - - /** - @dev Common function to handle the batch assignments. - */ - function _batchAssignCommon(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) private returns (uint64[] memory refs, address targetOwner) { - if (fragments.length != tokenIds.length) { - revert BadInputLengths(); - } - targetOwner = IERC721(target).ownerOf(targetTokenId); - refs = new uint64[](fragments.length); - for (uint i = 0; i < fragments.length; i++) { - address fragment = fragments[i]; - uint256 fragmentTokenId = tokenIds[i]; - refs[i] = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); - } - } - - /** - @notice Performs assignment of an IPatchworkAssignable to an IPatchworkLiteRef - @param fragment the IPatchworkAssignable's address - @param fragmentTokenId the IPatchworkAssignable's tokenId - @param target the IPatchworkLiteRef target's address - @param targetTokenId the IPatchworkLiteRef target's tokenId - @param targetOwner the owner address of the target - @return uint64 literef of assignable in target - */ - function _doAssign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, address targetOwner) private mustNotBeFrozen(fragment, fragmentTokenId) returns (uint64) { - if (fragment == target && fragmentTokenId == targetTokenId) { - revert SelfAssignmentNotAllowed(fragment, fragmentTokenId); - } - // Use the target's scope for general permission and check the fragment for detailed permissions - (uint256 scopeFee, uint256 protocolFee) = _doAssignPermissionsAndFees(fragment, fragmentTokenId, target, targetTokenId, targetOwner); - // Handle storage and duplicate checks - uint64 ref = _doAssignStorageAndDupes(fragment, fragmentTokenId, target, targetTokenId); - // these two end up beyond stack depth on some compiler settings. - emit Assign(targetOwner, fragment, fragmentTokenId, target, targetTokenId, scopeFee, protocolFee); - return ref; - } - - /** - @notice Handles assignment permissions and fees - @param fragment the IPatchworkAssignable's address - @param fragmentTokenId the IPatchworkAssignable's tokenId - @param target the IPatchworkLiteRef target's address - @param targetTokenId the IPatchworkLiteRef target's tokenId - @param targetOwner the owner address of the target - */ - function _doAssignPermissionsAndFees(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, address targetOwner) private returns (uint256 scopeFee, uint256 protocolFee) { - string memory targetScopeName = _getScopeName(target); - if (!IPatchworkAssignable(fragment).allowAssignment(fragmentTokenId, target, targetTokenId, targetOwner, msg.sender, targetScopeName)) { - revert NotAuthorized(msg.sender); - } - Scope storage targetScope = _mustHaveScope(targetScopeName); - _mustBeWhitelisted(targetScopeName, targetScope, target); - if (targetScope.owner == msg.sender || targetScope.operators[msg.sender]) { - // all good - } else if (targetScope.allowUserAssign) { - // msg.sender must own the target - if (targetOwner != msg.sender) { - revert NotAuthorized(msg.sender); - } - } else { - revert NotAuthorized(msg.sender); - } - if (_isLocked(fragment, fragmentTokenId)) { - revert Locked(fragment, fragmentTokenId); - } - // Whitelist check, these variables do not need to stay in the function level stack - string memory fragmentScopeName = _getScopeName(fragment); - Scope storage fragmentScope = _mustHaveScope(fragmentScopeName); - _mustBeWhitelisted(fragmentScopeName, fragmentScope, fragment); - (scopeFee, protocolFee) = _handleAssignFee(fragmentScopeName, fragmentScope, fragment); - } - - /** - @notice Handles assignment storage and duplicate checks - @param fragment the IPatchworkAssignable's address - @param fragmentTokenId the IPatchworkAssignable's tokenId - @param target the IPatchworkLiteRef target's address - @param targetTokenId the IPatchworkLiteRef target's tokenId - */ - function _doAssignStorageAndDupes(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) private returns (uint64 ref) { - bool redacted; - (ref, redacted) = IPatchworkLiteRef(target).getLiteReference(fragment, fragmentTokenId); - if (redacted) { - revert FragmentRedacted(address(fragment)); - } - if (ref == 0) { - revert FragmentUnregistered(address(fragment)); - } - // targetRef is a compound key (targetAddr+targetTokenID+ref) - blocks duplicate assignments - bytes32 targetRef = keccak256(abi.encodePacked(target, targetTokenId, ref)); - if (_liteRefs[targetRef]) { - revert FragmentAlreadyAssigned(address(fragment), fragmentTokenId); - } - // add to our storage of assignments - _liteRefs[targetRef] = true; - // call assign on the fragment - IPatchworkAssignable(fragment).assign(fragmentTokenId, target, targetTokenId); + function assignBatch(address[] calldata fragments, uint256[] calldata tokenIds, address target, uint256 targetTokenId, uint256 targetMetadataId) public payable { + _delegatecall(_assignerDelegate, abi.encodeWithSignature("assignBatch(address[],uint256[],address,uint256,uint256)", fragments, tokenIds, target, targetTokenId, targetMetadataId)); } /** @dev See {IPatchworkProtocol-unassign} */ - function unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { - _unassign(fragment, fragmentTokenId, target, targetTokenId, false, 0); + function unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public { + _delegatecall(_assignerDelegate, abi.encodeWithSignature("unassign(address,uint256,address,uint256)", fragment, fragmentTokenId, target, targetTokenId)); } /** @dev See {IPatchworkProtocol-unassign} */ - function unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { - _unassign(fragment, fragmentTokenId, target, targetTokenId, true, targetMetadataId); - } - - /** - @dev Common function to handle unassignments. - */ - function _unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool isDirect, uint256 targetMetadataId) private { - if (IERC165(fragment).supportsInterface(type(IPatchworkMultiAssignable).interfaceId)) { - if (isDirect) { - unassignMulti(fragment, fragmentTokenId, target, targetTokenId, targetMetadataId); - } else { - unassignMulti(fragment, fragmentTokenId, target, targetTokenId); - } - } else if (IERC165(fragment).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { - (address _target, uint256 _targetTokenId) = IPatchworkSingleAssignable(fragment).getAssignedTo(fragmentTokenId); - if (target != _target || _targetTokenId != targetTokenId) { - revert FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); - } - if (isDirect) { - unassignSingle(fragment, fragmentTokenId, targetMetadataId); - } else { - unassignSingle(fragment, fragmentTokenId); - } - } else { - revert UnsupportedContract(); - } + function unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public { + _delegatecall(_assignerDelegate, abi.encodeWithSignature("unassign(address,uint256,address,uint256,uint256)", fragment, fragmentTokenId, target, targetTokenId, targetMetadataId)); } /** @dev See {IPatchworkProtocol-unassignMulti} */ - function unassignMulti(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { - _unassignMultiCommon(fragment, fragmentTokenId, target, targetTokenId, false, 0); + function unassignMulti(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public { + _delegatecall(_assignerDelegate, abi.encodeWithSignature("unassignMulti(address,uint256,address,uint256)", fragment, fragmentTokenId, target, targetTokenId)); } /** @dev See {IPatchworkProtocol-unassignMulti} */ - function unassignMulti(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { - _unassignMultiCommon(fragment, fragmentTokenId, target, targetTokenId, true, targetMetadataId); - } - - /** - @dev Common function to handle the unassignment of multi assignables. - */ - function _unassignMultiCommon(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool isDirect, uint256 targetMetadataId) private { - IPatchworkMultiAssignable assignable = IPatchworkMultiAssignable(fragment); - if (!assignable.isAssignedTo(fragmentTokenId, target, targetTokenId)) { - revert FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); - } - string memory scopeName = _getScopeName(target); - _doUnassign(fragment, fragmentTokenId, target, targetTokenId, isDirect, targetMetadataId, scopeName); - assignable.unassign(fragmentTokenId, target, targetTokenId); + function unassignMulti(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public { + _delegatecall(_assignerDelegate, abi.encodeWithSignature("unassignMulti(address,uint256,address,uint256,uint256)", fragment, fragmentTokenId, target, targetTokenId, targetMetadataId)); } /** @dev See {IPatchworkProtocol-unassignSingle} */ - function unassignSingle(address fragment, uint fragmentTokenId) public mustNotBeFrozen(fragment, fragmentTokenId) { - _unassignSingleCommon(fragment, fragmentTokenId, false, 0); + function unassignSingle(address fragment, uint256 fragmentTokenId) public { + _delegatecall(_assignerDelegate, abi.encodeWithSignature("unassignSingle(address,uint256)", fragment, fragmentTokenId)); } /** @dev See {IPatchworkProtocol-unassignSingle} */ - function unassignSingle(address fragment, uint fragmentTokenId, uint256 targetMetadataId) public mustNotBeFrozen(fragment, fragmentTokenId) { - _unassignSingleCommon(fragment, fragmentTokenId, true, targetMetadataId); - } - - /** - @dev Common function to handle the unassignment of single assignables. - */ - function _unassignSingleCommon(address fragment, uint fragmentTokenId, bool isDirect, uint256 targetMetadataId) private { - IPatchworkSingleAssignable assignable = IPatchworkSingleAssignable(fragment); - (address target, uint256 targetTokenId) = assignable.getAssignedTo(fragmentTokenId); - if (target == address(0)) { - revert FragmentNotAssigned(fragment, fragmentTokenId); - } - string memory scopeName = _getScopeName(target); - _doUnassign(fragment, fragmentTokenId, target, targetTokenId, isDirect, targetMetadataId, scopeName); - assignable.unassign(fragmentTokenId); - } - - /** - @notice Performs unassignment of an IPatchworkAssignable to an IPatchworkLiteRef - @param fragment the IPatchworkAssignable's address - @param fragmentTokenId the IPatchworkAssignable's tokenId - @param target the IPatchworkLiteRef target's address - @param targetTokenId the IPatchworkLiteRef target's tokenId - @param direct If this is calling the direct function - @param targetMetadataId the metadataId to use on the target - @param scopeName the name of the target's scope - */ - function _doUnassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool direct, uint256 targetMetadataId, string memory scopeName) private { - Scope storage scope = _mustHaveScope(scopeName); - if (scope.owner == msg.sender || scope.operators[msg.sender]) { - // continue - } else if (scope.allowUserAssign) { - if (IERC721(target).ownerOf(targetTokenId) != msg.sender) { - revert NotAuthorized(msg.sender); - } - // continue - } else { - revert NotAuthorized(msg.sender); - } - (uint64 ref, ) = IPatchworkLiteRef(target).getLiteReference(fragment, fragmentTokenId); - if (ref == 0) { - revert FragmentUnregistered(address(fragment)); - } - bytes32 targetRef = keccak256(abi.encodePacked(target, targetTokenId, ref)); - if (!_liteRefs[targetRef]) { - revert RefNotFound(target, fragment, fragmentTokenId); - } - delete _liteRefs[targetRef]; - if (direct) { - IPatchworkLiteRef(target).removeReference(targetTokenId, ref, targetMetadataId); - } else { - IPatchworkLiteRef(target).removeReference(targetTokenId, ref); - } - - emit Unassign(IERC721(fragment).ownerOf(fragmentTokenId), fragment, fragmentTokenId, target, targetTokenId); + function unassignSingle(address fragment, uint256 fragmentTokenId, uint256 targetMetadataId) public { + _delegatecall(_assignerDelegate, abi.encodeWithSignature("unassignSingle(address,uint256,uint256)", fragment, fragmentTokenId, targetMetadataId)); } /** @@ -1000,28 +759,31 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } /** - @notice Requires that scopeName is present - @dev will revert with ScopeDoesNotExist if not present - @return scope the scope + @dev See {IPatchworkProtocol-proposeAssignerDelegate} */ - function _mustHaveScope(string memory scopeName) private view returns (Scope storage scope) { - scope = _scopes[scopeName]; - if (scope.owner == address(0)) { - revert ScopeDoesNotExist(scopeName); + function proposeAssignerDelegate(address addr) public onlyOwner { + if (addr == address(0)) { + // effectively a cancel + _proposedAssignerDelegate = ProposedAssignerDelegate(0, address(0)); + } else { + _proposedAssignerDelegate = ProposedAssignerDelegate(block.timestamp, addr); } + emit AssignerDelegatePropose(addr); } /** - @notice Requires that addr is whitelisted if whitelisting is enabled - @dev will revert with NotWhitelisted if whitelisting is enabled and address is not whitelisted - @param scopeName the name of the scope - @param scope the scope - @param addr the address to check + @dev See {IPatchworkProtocol-commitAssignerDelegate} */ - function _mustBeWhitelisted(string memory scopeName, Scope storage scope, address addr) private view { - if (scope.requireWhitelist && !scope.whitelist[addr]) { - revert NotWhitelisted(scopeName, addr); + function commitAssignerDelegate() public onlyOwner { + if (_proposedAssignerDelegate.timestamp == 0) { + revert NoDelegateProposed(); + } + if (block.timestamp < _proposedAssignerDelegate.timestamp + CONTRACT_UPGRADE_TIMELOCK) { + revert TimelockNotElapsed(); } + _assignerDelegate = _proposedAssignerDelegate.addr; + _proposedAssignerDelegate = ProposedAssignerDelegate(0, address(0)); + emit AssignerDelegateCommit(_assignerDelegate); } /** @@ -1046,68 +808,6 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } } - /** - @notice Requires that nft is not frozen - @dev will revert with Frozen if nft is frozen - @param nft the address of nft - @param tokenId the tokenId of nft - */ - modifier mustNotBeFrozen(address nft, uint256 tokenId) { - if (_isFrozen(nft, tokenId)) { - revert Frozen(nft, tokenId); - } - _; - } - - /** - @notice Determines if nft is frozen using ownership hierarchy - @param nft the address of nft - @param tokenId the tokenId of nft - @return frozen if the nft or an owner up the tree is frozen - */ - function _isFrozen(address nft, uint256 tokenId) private view returns (bool frozen) { - if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { - if (IPatchwork721(nft).frozen(tokenId)) { - return true; - } - if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { - (address assignedAddr, uint256 assignedTokenId) = IPatchworkSingleAssignable(nft).getAssignedTo(tokenId); - if (assignedAddr != address(0)) { - return _isFrozen(assignedAddr, assignedTokenId); - } - } - } - return false; - } - - /** - @notice Determines if nft is locked - @param nft the address of nft - @param tokenId the tokenId of nft - @return locked if the nft is locked - */ - function _isLocked(address nft, uint256 tokenId) private view returns (bool locked) { - if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { - if (IPatchwork721(nft).locked(tokenId)) { - return true; - } - } - return false; - } - - /** - @notice Memoizing wrapper for IPatchworkScoped.getScopeName() - @param addr Address to check - @return scopeName return value of IPatchworkScoped(addr).getScopeName() - */ - function _getScopeName(address addr) private returns (string memory scopeName) { - scopeName = _scopeNameCache[addr]; - if (bytes(scopeName).length == 0) { - scopeName = IPatchworkScoped(addr).getScopeName(); - _scopeNameCache[addr] = scopeName; - } - } - /** @notice Memoized view-only wrapper for IPatchworkScoped.getScopeName() @dev required to get optimized result from view-only functions, does not memoize result if not already memoized @@ -1121,6 +821,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { } } + /// Only protocol owner or protocol banker modifier onlyProtoOwnerBanker() { if (msg.sender != owner() && _protocolBankers[msg.sender] == false) { revert NotAuthorized(msg.sender); @@ -1128,6 +829,7 @@ contract PatchworkProtocol is IPatchworkProtocol, Ownable, ReentrancyGuard { _; } + /// Only msg.sender from addr modifier onlyFrom(address addr) { if (msg.sender != addr) { revert NotAuthorized(msg.sender); diff --git a/src/PatchworkProtocolAssigner.sol b/src/PatchworkProtocolAssigner.sol new file mode 100644 index 0000000..0c5416b --- /dev/null +++ b/src/PatchworkProtocolAssigner.sol @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +/** + + ____ __ __ __ + / __ \____ _/ /______/ /_ _ ______ _____/ /__ + / /_/ / __ `/ __/ ___/ __ \ | /| / / __ \/ ___/ //_/ + / ____/ /_/ / /_/ /__/ / / / |/ |/ / /_/ / / / ,< +/_/ ___\__,_/\__/\___/_/ /_/|__/|__/\____/_/ /_/|_| + / __ \_________ / /_____ _________ / / + / /_/ / ___/ __ \/ __/ __ \/ ___/ __ \/ / + / ____/ / / /_/ / /_/ /_/ / /__/ /_/ / / +/_/ /_/ \____/\__/\____/\___/\____/_/ + +Assigner Module + +*/ + +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "./PatchworkProtocolCommon.sol"; +import "./interfaces/IPatchwork721.sol"; +import "./interfaces/IPatchworkSingleAssignable.sol"; +import "./interfaces/IPatchworkMultiAssignable.sol"; +import "./interfaces/IPatchworkLiteRef.sol"; + +/** +@title Patchwork Protocol Assigner Module +@author Runic Labs, Inc +*/ +contract PatchworkProtocolAssigner is PatchworkProtocolCommon { + + /// The denominator for fee basis points + uint256 private constant _FEE_BASIS_DENOM = 10000; + + constructor(address owner_) PatchworkProtocolCommon(owner_) {} + + // common to assigns + function _handleAssignFee(string memory scopeName, IPatchworkProtocol.Scope storage scope, address fragmentAddress) private returns (uint256 scopeFee, uint256 protocolFee) { + uint256 assignFee = scope.assignFees[fragmentAddress]; + if (msg.value != assignFee) { + revert IPatchworkProtocol.IncorrectFeeAmount(); + } + if (msg.value > 0) { + uint256 assignBp; + IPatchworkProtocol.FeeConfigOverride storage feeOverride = _scopeFeeOverrides[scopeName]; + if (feeOverride.active) { + assignBp = feeOverride.assignBp; + } else { + assignBp = _protocolFeeConfig.assignBp; + } + protocolFee = msg.value * assignBp / _FEE_BASIS_DENOM; + scopeFee = msg.value - protocolFee; + _protocolBalance += protocolFee; + scope.balance += scopeFee; + } + } + + /** + @dev See {IPatchworkProtocol-assign} + */ + function assign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public payable mustNotBeFrozen(target, targetTokenId) { + address targetOwner = IERC721(target).ownerOf(targetTokenId); + uint64 ref = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); + IPatchworkLiteRef(target).addReference(targetTokenId, ref); + } + + /** + @dev See {IPatchworkProtocol-assign} + */ + function assign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public payable mustNotBeFrozen(target, targetTokenId) { + address targetOwner = IERC721(target).ownerOf(targetTokenId); + uint64 ref = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); + IPatchworkLiteRef(target).addReference(targetTokenId, ref, targetMetadataId); + } + + /** + @dev See {IPatchworkProtocol-assignBatch} + */ + function assignBatch(address[] calldata fragments, uint256[] calldata tokenIds, address target, uint256 targetTokenId) public payable mustNotBeFrozen(target, targetTokenId) { + (uint64[] memory refs, ) = _batchAssignCommon(fragments, tokenIds, target, targetTokenId); + IPatchworkLiteRef(target).addReferenceBatch(targetTokenId, refs); + } + + /** + @dev See {IPatchworkProtocol-assignBatch} + */ + function assignBatch(address[] calldata fragments, uint256[] calldata tokenIds, address target, uint256 targetTokenId, uint256 targetMetadataId) public payable mustNotBeFrozen(target, targetTokenId) { + (uint64[] memory refs, ) = _batchAssignCommon(fragments, tokenIds, target, targetTokenId); + IPatchworkLiteRef(target).addReferenceBatch(targetTokenId, refs, targetMetadataId); + } + + /** + @dev Common function to handle the batch assignments. + */ + function _batchAssignCommon(address[] calldata fragments, uint256[] calldata tokenIds, address target, uint256 targetTokenId) private returns (uint64[] memory refs, address targetOwner) { + if (fragments.length != tokenIds.length) { + revert IPatchworkProtocol.BadInputLengths(); + } + targetOwner = IERC721(target).ownerOf(targetTokenId); + refs = new uint64[](fragments.length); + for (uint i = 0; i < fragments.length; i++) { + address fragment = fragments[i]; + uint256 fragmentTokenId = tokenIds[i]; + refs[i] = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); + } + } + + /** + @notice Performs assignment of an IPatchworkAssignable to an IPatchworkLiteRef + @param fragment the IPatchworkAssignable's address + @param fragmentTokenId the IPatchworkAssignable's tokenId + @param target the IPatchworkLiteRef target's address + @param targetTokenId the IPatchworkLiteRef target's tokenId + @param targetOwner the owner address of the target + @return uint64 literef of assignable in target + */ + function _doAssign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, address targetOwner) private mustNotBeFrozen(fragment, fragmentTokenId) returns (uint64) { + if (fragment == target && fragmentTokenId == targetTokenId) { + revert IPatchworkProtocol.SelfAssignmentNotAllowed(fragment, fragmentTokenId); + } + // Use the target's scope for general permission and check the fragment for detailed permissions + (uint256 scopeFee, uint256 protocolFee) = _doAssignPermissionsAndFees(fragment, fragmentTokenId, target, targetTokenId, targetOwner); + // Handle storage and duplicate checks + uint64 ref = _doAssignStorageAndDupes(fragment, fragmentTokenId, target, targetTokenId); + // these two end up beyond stack depth on some compiler settings. + emit IPatchworkProtocol.Assign(targetOwner, fragment, fragmentTokenId, target, targetTokenId, scopeFee, protocolFee); + return ref; + } + + /** + @notice Handles assignment permissions and fees + @param fragment the IPatchworkAssignable's address + @param fragmentTokenId the IPatchworkAssignable's tokenId + @param target the IPatchworkLiteRef target's address + @param targetTokenId the IPatchworkLiteRef target's tokenId + @param targetOwner the owner address of the target + */ + function _doAssignPermissionsAndFees(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, address targetOwner) private returns (uint256 scopeFee, uint256 protocolFee) { + string memory targetScopeName = _getScopeName(target); + if (!IPatchworkAssignable(fragment).allowAssignment(fragmentTokenId, target, targetTokenId, targetOwner, msg.sender, targetScopeName)) { + revert IPatchworkProtocol.NotAuthorized(msg.sender); + } + IPatchworkProtocol.Scope storage targetScope = _mustHaveScope(targetScopeName); + _mustBeWhitelisted(targetScopeName, targetScope, target); + if (targetScope.owner == msg.sender || targetScope.operators[msg.sender]) { + // all good + } else if (targetScope.allowUserAssign) { + // msg.sender must own the target + if (targetOwner != msg.sender) { + revert IPatchworkProtocol.NotAuthorized(msg.sender); + } + } else { + revert IPatchworkProtocol.NotAuthorized(msg.sender); + } + if (_isLocked(fragment, fragmentTokenId)) { + revert IPatchworkProtocol.Locked(fragment, fragmentTokenId); + } + // Whitelist check, these variables do not need to stay in the function level stack + string memory fragmentScopeName = _getScopeName(fragment); + IPatchworkProtocol.Scope storage fragmentScope = _mustHaveScope(fragmentScopeName); + _mustBeWhitelisted(fragmentScopeName, fragmentScope, fragment); + (scopeFee, protocolFee) = _handleAssignFee(fragmentScopeName, fragmentScope, fragment); + } + + /** + @notice Handles assignment storage and duplicate checks + @param fragment the IPatchworkAssignable's address + @param fragmentTokenId the IPatchworkAssignable's tokenId + @param target the IPatchworkLiteRef target's address + @param targetTokenId the IPatchworkLiteRef target's tokenId + */ + function _doAssignStorageAndDupes(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) private returns (uint64 ref) { + bool redacted; + (ref, redacted) = IPatchworkLiteRef(target).getLiteReference(fragment, fragmentTokenId); + if (redacted) { + revert IPatchworkProtocol.FragmentRedacted(address(fragment)); + } + if (ref == 0) { + revert IPatchworkProtocol.FragmentUnregistered(address(fragment)); + } + // targetRef is a compound key (targetAddr+targetTokenID+ref) - blocks duplicate assignments + bytes32 targetRef = keccak256(abi.encodePacked(target, targetTokenId, ref)); + if (_liteRefs[targetRef]) { + revert IPatchworkProtocol.FragmentAlreadyAssigned(address(fragment), fragmentTokenId); + } + // add to our storage of assignments + _liteRefs[targetRef] = true; + // call assign on the fragment + IPatchworkAssignable(fragment).assign(fragmentTokenId, target, targetTokenId); + } + + /** + @dev See {IPatchworkProtocol-unassign} + */ + function unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { + _unassign(fragment, fragmentTokenId, target, targetTokenId, false, 0); + } + + /** + @dev See {IPatchworkProtocol-unassign} + */ + function unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { + _unassign(fragment, fragmentTokenId, target, targetTokenId, true, targetMetadataId); + } + + /** + @dev Common function to handle unassignments. + */ + function _unassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool isDirect, uint256 targetMetadataId) private { + if (IERC165(fragment).supportsInterface(type(IPatchworkMultiAssignable).interfaceId)) { + if (isDirect) { + unassignMulti(fragment, fragmentTokenId, target, targetTokenId, targetMetadataId); + } else { + unassignMulti(fragment, fragmentTokenId, target, targetTokenId); + } + } else if (IERC165(fragment).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { + (address _target, uint256 _targetTokenId) = IPatchworkSingleAssignable(fragment).getAssignedTo(fragmentTokenId); + if (target != _target || _targetTokenId != targetTokenId) { + revert IPatchworkProtocol.FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); + } + if (isDirect) { + unassignSingle(fragment, fragmentTokenId, targetMetadataId); + } else { + unassignSingle(fragment, fragmentTokenId); + } + } else { + revert IPatchworkProtocol.UnsupportedContract(); + } + } + + /** + @dev See {IPatchworkProtocol-unassignMulti} + */ + function unassignMulti(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public mustNotBeFrozen(target, targetTokenId) { + _unassignMultiCommon(fragment, fragmentTokenId, target, targetTokenId, false, 0); + } + + /** + @dev See {IPatchworkProtocol-unassignMulti} + */ + function unassignMulti(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public mustNotBeFrozen(target, targetTokenId) { + _unassignMultiCommon(fragment, fragmentTokenId, target, targetTokenId, true, targetMetadataId); + } + + /** + @dev Common function to handle the unassignment of multi assignables. + */ + function _unassignMultiCommon(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool isDirect, uint256 targetMetadataId) private { + IPatchworkMultiAssignable assignable = IPatchworkMultiAssignable(fragment); + if (!assignable.isAssignedTo(fragmentTokenId, target, targetTokenId)) { + revert IPatchworkProtocol.FragmentNotAssignedToTarget(fragment, fragmentTokenId, target, targetTokenId); + } + string memory scopeName = _getScopeName(target); + _doUnassign(fragment, fragmentTokenId, target, targetTokenId, isDirect, targetMetadataId, scopeName); + assignable.unassign(fragmentTokenId, target, targetTokenId); + } + + /** + @dev See {IPatchworkProtocol-unassignSingle} + */ + function unassignSingle(address fragment, uint256 fragmentTokenId) public mustNotBeFrozen(fragment, fragmentTokenId) { + _unassignSingleCommon(fragment, fragmentTokenId, false, 0); + } + + /** + @dev See {IPatchworkProtocol-unassignSingle} + */ + function unassignSingle(address fragment, uint256 fragmentTokenId, uint256 targetMetadataId) public mustNotBeFrozen(fragment, fragmentTokenId) { + _unassignSingleCommon(fragment, fragmentTokenId, true, targetMetadataId); + } + + /** + @dev Common function to handle the unassignment of single assignables. + */ + function _unassignSingleCommon(address fragment, uint256 fragmentTokenId, bool isDirect, uint256 targetMetadataId) private { + IPatchworkSingleAssignable assignable = IPatchworkSingleAssignable(fragment); + (address target, uint256 targetTokenId) = assignable.getAssignedTo(fragmentTokenId); + if (target == address(0)) { + revert IPatchworkProtocol.FragmentNotAssigned(fragment, fragmentTokenId); + } + string memory scopeName = _getScopeName(target); + _doUnassign(fragment, fragmentTokenId, target, targetTokenId, isDirect, targetMetadataId, scopeName); + assignable.unassign(fragmentTokenId); + } + + /** + @notice Performs unassignment of an IPatchworkAssignable to an IPatchworkLiteRef + @param fragment the IPatchworkAssignable's address + @param fragmentTokenId the IPatchworkAssignable's tokenId + @param target the IPatchworkLiteRef target's address + @param targetTokenId the IPatchworkLiteRef target's tokenId + @param direct If this is calling the direct function + @param targetMetadataId the metadataId to use on the target + @param scopeName the name of the target's scope + */ + function _doUnassign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, bool direct, uint256 targetMetadataId, string memory scopeName) private { + IPatchworkProtocol.Scope storage scope = _mustHaveScope(scopeName); + if (scope.owner == msg.sender || scope.operators[msg.sender]) { + // continue + } else if (scope.allowUserAssign) { + if (IERC721(target).ownerOf(targetTokenId) != msg.sender) { + revert IPatchworkProtocol.NotAuthorized(msg.sender); + } + // continue + } else { + revert IPatchworkProtocol.NotAuthorized(msg.sender); + } + (uint64 ref, ) = IPatchworkLiteRef(target).getLiteReference(fragment, fragmentTokenId); + if (ref == 0) { + revert IPatchworkProtocol.FragmentUnregistered(address(fragment)); + } + bytes32 targetRef = keccak256(abi.encodePacked(target, targetTokenId, ref)); + if (!_liteRefs[targetRef]) { + revert IPatchworkProtocol.RefNotFound(target, fragment, fragmentTokenId); + } + delete _liteRefs[targetRef]; + if (direct) { + IPatchworkLiteRef(target).removeReference(targetTokenId, ref, targetMetadataId); + } else { + IPatchworkLiteRef(target).removeReference(targetTokenId, ref); + } + emit IPatchworkProtocol.Unassign(IERC721(fragment).ownerOf(fragmentTokenId), fragment, fragmentTokenId, target, targetTokenId); + } + + /** + @notice Requires that nft is not frozen + @dev will revert with Frozen if nft is frozen + @param nft the address of nft + @param tokenId the tokenId of nft + */ + modifier mustNotBeFrozen(address nft, uint256 tokenId) { + if (_isFrozen(nft, tokenId)) { + revert IPatchworkProtocol.Frozen(nft, tokenId); + } + _; + } + + /** + @notice Determines if nft is frozen using ownership hierarchy + @param nft the address of nft + @param tokenId the tokenId of nft + @return frozen if the nft or an owner up the tree is frozen + */ + function _isFrozen(address nft, uint256 tokenId) private view returns (bool frozen) { + if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { + if (IPatchwork721(nft).frozen(tokenId)) { + return true; + } + if (IERC165(nft).supportsInterface(type(IPatchworkSingleAssignable).interfaceId)) { + (address assignedAddr, uint256 assignedTokenId) = IPatchworkSingleAssignable(nft).getAssignedTo(tokenId); + if (assignedAddr != address(0)) { + return _isFrozen(assignedAddr, assignedTokenId); + } + } + } + return false; + } + + /** + @notice Determines if nft is locked + @param nft the address of nft + @param tokenId the tokenId of nft + @return locked if the nft is locked + */ + function _isLocked(address nft, uint256 tokenId) private view returns (bool locked) { + if (IERC165(nft).supportsInterface(type(IPatchwork721).interfaceId)) { + if (IPatchwork721(nft).locked(tokenId)) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/PatchworkProtocolCommon.sol b/src/PatchworkProtocolCommon.sol new file mode 100644 index 0000000..915f811 --- /dev/null +++ b/src/PatchworkProtocolCommon.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +/** + + ____ __ __ __ + / __ \____ _/ /______/ /_ _ ______ _____/ /__ + / /_/ / __ `/ __/ ___/ __ \ | /| / / __ \/ ___/ //_/ + / ____/ /_/ / /_/ /__/ / / / |/ |/ / /_/ / / / ,< +/_/ ___\__,_/\__/\___/_/ /_/|__/|__/\____/_/ /_/|_| + / __ \_________ / /_____ _________ / / + / /_/ / ___/ __ \/ __/ __ \/ ___/ __ \/ / + / ____/ / / /_/ / /_/ /_/ / /__/ /_/ / / +/_/ /_/ \____/\__/\____/\___/\____/_/ + +Storage layout and common functions + +*/ + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "./interfaces/IPatchworkProtocol.sol"; +import "./interfaces/IPatchworkScoped.sol"; + +/** +@title Patchwork Protocol Storage layout and common functions +@author Runic Labs, Inc +*/ +contract PatchworkProtocolCommon is Ownable, ReentrancyGuard{ + + constructor(address owner_) Ownable(owner_) {} + + /// Scopes + mapping(string => IPatchworkProtocol.Scope) internal _scopes; + + /** + @notice unique references + @dev A hash of target + targetTokenId + literef provides uniqueness + */ + mapping(bytes32 => bool) internal _liteRefs; + + /** + @notice unique patches + @dev Hash of the patch mapped to a boolean indicating its uniqueness + */ + mapping(bytes32 => bool) internal _uniquePatches; + + /// Balance of the protocol + uint256 internal _protocolBalance; + + /** + @notice protocol bankers + @dev Map of addresses authorized to set fees and withdraw funds for the protocol + @dev Does not allow for scope balance withdrawl + */ + mapping(address => bool) internal _protocolBankers; + + /// Current protocol fee configuration + IPatchworkProtocol.FeeConfig internal _protocolFeeConfig; + + /// Proposed protocol fee configuration + mapping(string => IPatchworkProtocol.ProposedFeeConfig) internal _proposedFeeConfigs; + + /// scope-based fee overrides + mapping(string => IPatchworkProtocol.FeeConfigOverride) internal _scopeFeeOverrides; + + /// Scope name cache + mapping(address => string) internal _scopeNameCache; + + /// Proposed assigner delegate + IPatchworkProtocol.ProposedAssignerDelegate internal _proposedAssignerDelegate; + + /// Assigner module + address internal _assignerDelegate; + + /** + @notice Memoizing wrapper for IPatchworkScoped.getScopeName() + @param addr Address to check + @return scopeName return value of IPatchworkScoped(addr).getScopeName() + */ + function _getScopeName(address addr) internal returns (string memory scopeName) { + scopeName = _scopeNameCache[addr]; + if (bytes(scopeName).length == 0) { + scopeName = IPatchworkScoped(addr).getScopeName(); + _scopeNameCache[addr] = scopeName; + } + } + + /** + @notice Requires that scopeName is present + @dev will revert with ScopeDoesNotExist if not present + @return scope the scope + */ + function _mustHaveScope(string memory scopeName) internal view returns (IPatchworkProtocol.Scope storage scope) { + scope = _scopes[scopeName]; + if (scope.owner == address(0)) { + revert IPatchworkProtocol.ScopeDoesNotExist(scopeName); + } + } + + /** + @notice Requires that addr is whitelisted if whitelisting is enabled + @dev will revert with NotWhitelisted if whitelisting is enabled and address is not whitelisted + @param scopeName the name of the scope + @param scope the scope + @param addr the address to check + */ + function _mustBeWhitelisted(string memory scopeName, IPatchworkProtocol.Scope storage scope, address addr) internal view { + if (scope.requireWhitelist && !scope.whitelist[addr]) { + revert IPatchworkProtocol.NotWhitelisted(scopeName, addr); + } + } +} \ No newline at end of file diff --git a/src/PatchworkUtils.sol b/src/PatchworkUtils.sol index 5ad07b4..83ef7ad 100644 --- a/src/PatchworkUtils.sol +++ b/src/PatchworkUtils.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; /** @title Patchwork Contract Utilities diff --git a/src/interfaces/IERC4906.sol b/src/interfaces/IERC4906.sol index dd2cb23..747153b 100644 --- a/src/interfaces/IERC4906.sol +++ b/src/interfaces/IERC4906.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; +pragma solidity ^0.8.23; import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; diff --git a/src/interfaces/IERC5192.sol b/src/interfaces/IERC5192.sol index db3110f..3982143 100644 --- a/src/interfaces/IERC5192.sol +++ b/src/interfaces/IERC5192.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; +pragma solidity ^0.8.23; interface IERC5192 { /// @notice Emitted when the locking status is changed to locked. diff --git a/src/interfaces/IPatchwork1155Patch.sol b/src/interfaces/IPatchwork1155Patch.sol index 908e4b3..3e49ed9 100644 --- a/src/interfaces/IPatchwork1155Patch.sol +++ b/src/interfaces/IPatchwork1155Patch.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "./IPatchworkScoped.sol"; diff --git a/src/interfaces/IPatchwork721.sol b/src/interfaces/IPatchwork721.sol index c9aa0b2..3409856 100644 --- a/src/interfaces/IPatchwork721.sol +++ b/src/interfaces/IPatchwork721.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "./IERC5192.sol"; diff --git a/src/interfaces/IPatchworkAccountPatch.sol b/src/interfaces/IPatchworkAccountPatch.sol index 5045a68..60c6a36 100644 --- a/src/interfaces/IPatchworkAccountPatch.sol +++ b/src/interfaces/IPatchworkAccountPatch.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "./IPatchworkScoped.sol"; diff --git a/src/interfaces/IPatchworkAssignable.sol b/src/interfaces/IPatchworkAssignable.sol index a666b3e..77ccd85 100644 --- a/src/interfaces/IPatchworkAssignable.sol +++ b/src/interfaces/IPatchworkAssignable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "./IPatchworkScoped.sol"; diff --git a/src/interfaces/IPatchworkLiteRef.sol b/src/interfaces/IPatchworkLiteRef.sol index dbab547..9ec46f6 100644 --- a/src/interfaces/IPatchworkLiteRef.sol +++ b/src/interfaces/IPatchworkLiteRef.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; /** @title Patchwork Protocol LiteRef NFT Interface diff --git a/src/interfaces/IPatchworkMintable.sol b/src/interfaces/IPatchworkMintable.sol index 419acf5..f90a1c6 100644 --- a/src/interfaces/IPatchworkMintable.sol +++ b/src/interfaces/IPatchworkMintable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "./IPatchworkScoped.sol"; diff --git a/src/interfaces/IPatchworkMultiAssignable.sol b/src/interfaces/IPatchworkMultiAssignable.sol index 387ba9a..d7679dc 100644 --- a/src/interfaces/IPatchworkMultiAssignable.sol +++ b/src/interfaces/IPatchworkMultiAssignable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "./IPatchworkAssignable.sol"; diff --git a/src/interfaces/IPatchworkPatch.sol b/src/interfaces/IPatchworkPatch.sol index f85bdc8..0b96cc5 100644 --- a/src/interfaces/IPatchworkPatch.sol +++ b/src/interfaces/IPatchworkPatch.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "./IPatchworkScoped.sol"; diff --git a/src/interfaces/IPatchworkProtocol.sol b/src/interfaces/IPatchworkProtocol.sol index 36ec111..e249de1 100644 --- a/src/interfaces/IPatchworkProtocol.sol +++ b/src/interfaces/IPatchworkProtocol.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; /** @title Patchwork Protocol Interface @@ -254,6 +254,11 @@ interface IPatchworkProtocol { */ error InvalidFeeValue(); + /** + @notice No delegate proposed + */ + error NoDelegateProposed(); + /** @notice Fee Configuration */ @@ -290,6 +295,14 @@ interface IPatchworkProtocol { bool active; /// If the mint is active } + /** + @notice Proposed assigner delegate + */ + struct ProposedAssignerDelegate { + uint256 timestamp; + address addr; + } + /** @notice Represents a defined scope within the system @dev Contains details about the scope ownership, permissions, and mappings for references and assignments @@ -580,34 +593,58 @@ interface IPatchworkProtocol { /** @notice Emitted on protocol fee config proposed + @param config The fee configuration */ event ProtocolFeeConfigPropose(FeeConfig config); /** @notice Emitted on protocol fee config committed + @param config The fee configuration */ event ProtocolFeeConfigCommit(FeeConfig config); /** @notice Emitted on scope fee config override proposed + @param scopeName The scope + @param config The fee configuration */ event ScopeFeeOverridePropose(string scopeName, FeeConfigOverride config); /** @notice Emitted on scope fee config override committed + @param scopeName The scope + @param config The fee configuration */ event ScopeFeeOverrideCommit(string scopeName, FeeConfigOverride config); /** - @notice Emitted on patch fee change + @notice Emitted on patch fee change + @param scopeName The scope of the patch + @param addr The address of the patch + @param fee The new fee */ event PatchFeeChange(string scopeName, address indexed addr, uint256 fee); /** @notice Emitted on assign fee change + @param scopeName The scope of the assignable + @param addr The address of the assignable + @param fee The new fee */ event AssignFeeChange(string scopeName, address indexed addr, uint256 fee); + /** + @notice Emitted on assigner delegate propose + @param addr The address of the delegate + */ + event AssignerDelegatePropose(address indexed addr); + + /** + @notice Emitted on assigner delegate commit + @param addr The address of the delegate + */ + event AssignerDelegateCommit(address indexed addr); + /** @notice Claim a scope @param scopeName the name of the scope @@ -931,7 +968,7 @@ interface IPatchworkProtocol { @param target The address of the target IPatchworkLiteRef @param targetTokenId The token ID of the target IPatchworkLiteRef */ - function assignBatch(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId) external payable; + function assignBatch(address[] calldata fragments, uint256[] calldata tokenIds, address target, uint256 targetTokenId) external payable; /** @notice Assign multiple fragments to a target in batch @@ -941,7 +978,7 @@ interface IPatchworkProtocol { @param targetTokenId The token ID of the target IPatchworkLiteRef @param targetMetadataId The metadata ID on the target to store the references in */ - function assignBatch(address[] calldata fragments, uint[] calldata tokenIds, address target, uint targetTokenId, uint256 targetMetadataId) external payable; + function assignBatch(address[] calldata fragments, uint256[] calldata tokenIds, address target, uint256 targetTokenId, uint256 targetMetadataId) external payable; /** @notice Unassign a fragment from a target @@ -949,7 +986,7 @@ interface IPatchworkProtocol { @param fragmentTokenId The IPatchworkSingleAssignable token ID of the fragment @dev reverts if fragment is not an IPatchworkSingleAssignable */ - function unassignSingle(address fragment, uint fragmentTokenId) external; + function unassignSingle(address fragment, uint256 fragmentTokenId) external; /** @notice Unassign a fragment from a target @@ -958,7 +995,7 @@ interface IPatchworkProtocol { @param targetMetadataId The metadata ID on the target to unassign from @dev reverts if fragment is not an IPatchworkSingleAssignable */ - function unassignSingle(address fragment, uint fragmentTokenId, uint256 targetMetadataId) external; + function unassignSingle(address fragment, uint256 fragmentTokenId, uint256 targetMetadataId) external; /** @notice Unassigns a multi relation @@ -1014,4 +1051,16 @@ interface IPatchworkProtocol { @param tokenId The ID of the token whose ownership tree needs to be updated */ function updateOwnershipTree(address addr, uint256 tokenId) external; + + /** + @notice Propose an assigner delegate module + @param addr The address of the new delegate module + */ + function proposeAssignerDelegate(address addr) external; + + /** + @notice Commit the proposed assigner delegate module + @dev must be past timelock + */ + function commitAssignerDelegate() external; } \ No newline at end of file diff --git a/src/interfaces/IPatchworkScoped.sol b/src/interfaces/IPatchworkScoped.sol index e1af777..5e1266a 100644 --- a/src/interfaces/IPatchworkScoped.sol +++ b/src/interfaces/IPatchworkScoped.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; /** @title Patchwork Protocol Scoped Interface diff --git a/src/interfaces/IPatchworkSingleAssignable.sol b/src/interfaces/IPatchworkSingleAssignable.sol index 5d6ac99..b6ccd63 100644 --- a/src/interfaces/IPatchworkSingleAssignable.sol +++ b/src/interfaces/IPatchworkSingleAssignable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "./IPatchworkAssignable.sol"; diff --git a/test/AssignerDelegate.t.sol b/test/AssignerDelegate.t.sol new file mode 100644 index 0000000..6820976 --- /dev/null +++ b/test/AssignerDelegate.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.23; + +import "forge-std/Test.sol"; +import "../src/PatchworkProtocol.sol"; +import "../src/PatchworkProtocolAssigner.sol"; + +contract AssignerDelegateTest is Test { + + PatchworkProtocol _prot; + address _defaultUser; + address _patchworkOwner; + address _userAddress; + address _user2Address; + + function setUp() public { + _defaultUser = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496; + _patchworkOwner = 0xF09CFF10D85E70D5AA94c85ebBEbD288756EFEd5; + _userAddress = 0x10E4017cEd8648A9D5dAc21C82589C03C4835CCc; + _user2Address = address(550001); + + vm.startPrank(_patchworkOwner); + _prot = new PatchworkProtocol(_patchworkOwner, address(new PatchworkProtocolAssigner(_patchworkOwner))); + vm.stopPrank(); + } + + function testTimelock() public { + // test wrong user + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, _defaultUser)); + _prot.commitAssignerDelegate(); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, _defaultUser)); + _prot.proposeAssignerDelegate(address(5)); + + // test no proposal + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NoDelegateProposed.selector)); + vm.prank(_patchworkOwner); + _prot.commitAssignerDelegate(); + + // happy path propose + vm.prank(_patchworkOwner); + _prot.proposeAssignerDelegate(address(5)); + + // timelock not met + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.TimelockNotElapsed.selector)); + vm.prank(_patchworkOwner); + _prot.commitAssignerDelegate(); + + // test cancel + vm.prank(_patchworkOwner); + _prot.proposeAssignerDelegate(address(0)); + + // no proposal after cancel + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NoDelegateProposed.selector)); + vm.prank(_patchworkOwner); + _prot.commitAssignerDelegate(); + + // happy path propose, skip and commit + vm.prank(_patchworkOwner); + _prot.proposeAssignerDelegate(address(5)); + skip(2000000); + vm.prank(_patchworkOwner); + _prot.commitAssignerDelegate(); + + // no proposal after commit + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NoDelegateProposed.selector)); + vm.prank(_patchworkOwner); + _prot.commitAssignerDelegate(); + } +} \ No newline at end of file diff --git a/test/Fees.t.sol b/test/Fees.t.sol index f892e16..3fead76 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; +import "../src/PatchworkProtocolAssigner.sol"; import "./nfts/Test1155PatchNFT.sol"; import "./nfts/TestBase1155.sol"; import "./nfts/TestFragmentLiteRefNFT.sol"; @@ -32,13 +33,12 @@ contract FeesTest is Test { _user2Address = address(550001); _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; - vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(_patchworkOwner); - vm.prank(_patchworkOwner); + vm.startPrank(_patchworkOwner); + _prot = new PatchworkProtocol(_patchworkOwner, address(new PatchworkProtocolAssigner(_patchworkOwner))); _prot.proposeProtocolFeeConfig(IPatchworkProtocol.FeeConfig(1000, 1000, 1000)); // 10%, 10%, 10% skip(20000000); - vm.prank(_patchworkOwner); _prot.commitProtocolFeeConfig(); + vm.stopPrank(); vm.startPrank(_scopeOwner); _scopeName = "testscope"; diff --git a/test/Gas.t.sol b/test/Gas.t.sol index 7a1966a..d88654c 100644 --- a/test/Gas.t.sol +++ b/test/Gas.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; diff --git a/test/Patchwork1155Patch.t.sol b/test/Patchwork1155Patch.t.sol index dedce46..692757d 100644 --- a/test/Patchwork1155Patch.t.sol +++ b/test/Patchwork1155Patch.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; +import "../src/PatchworkProtocolAssigner.sol"; import "./nfts/Test1155PatchNFT.sol"; import "./nfts/TestBase1155.sol"; @@ -27,7 +28,7 @@ contract Patchwork1155PatchTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(_patchworkOwner); + _prot = new PatchworkProtocol(_patchworkOwner, address(new PatchworkProtocolAssigner(_patchworkOwner))); vm.startPrank(_scopeOwner); _scopeName = "testscope"; diff --git a/test/PatchworkAccountPatch.t.sol b/test/PatchworkAccountPatch.t.sol index e68dbed..a8069bc 100644 --- a/test/PatchworkAccountPatch.t.sol +++ b/test/PatchworkAccountPatch.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; +import "../src/PatchworkProtocolAssigner.sol"; import "./nfts/TestAccountPatchNFT.sol"; contract PatchworkAccountPatchTest is Test { @@ -26,7 +27,7 @@ contract PatchworkAccountPatchTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(_patchworkOwner); + _prot = new PatchworkProtocol(_patchworkOwner, address(new PatchworkProtocolAssigner(_patchworkOwner))); vm.startPrank(_scopeOwner); _scopeName = "testscope"; diff --git a/test/PatchworkFragmentMulti.t.sol b/test/PatchworkFragmentMulti.t.sol index 7b2d075..9ce0ee9 100644 --- a/test/PatchworkFragmentMulti.t.sol +++ b/test/PatchworkFragmentMulti.t.sol @@ -5,6 +5,7 @@ import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; +import "../src/PatchworkProtocolAssigner.sol"; import "./nfts/TestFragmentLiteRefNFT.sol"; import "./nfts/TestBaseNFT.sol"; import "./nfts/TestMultiFragmentNFT.sol"; @@ -29,7 +30,7 @@ contract PatchworkFragmentMultiTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(_patchworkOwner); + _prot = new PatchworkProtocol(_patchworkOwner, address(new PatchworkProtocolAssigner(_patchworkOwner))); _scopeName = "testscope"; vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); diff --git a/test/PatchworkFragmentSingle.t.sol b/test/PatchworkFragmentSingle.t.sol index 221e9b9..a7006a6 100644 --- a/test/PatchworkFragmentSingle.t.sol +++ b/test/PatchworkFragmentSingle.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; +import "../src/PatchworkProtocolAssigner.sol"; import "./nfts/TestFragmentLiteRefNFT.sol"; import "./nfts/TestBaseNFT.sol"; @@ -27,7 +28,7 @@ contract PatchworkFragmentSingleTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(_patchworkOwner); + _prot = new PatchworkProtocol(_patchworkOwner, address(new PatchworkProtocolAssigner(_patchworkOwner))); _scopeName = "testscope"; vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); diff --git a/test/PatchworkNFT.t.sol b/test/PatchworkNFT.t.sol index dddd2bd..ba88887 100644 --- a/test/PatchworkNFT.t.sol +++ b/test/PatchworkNFT.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; +import "../src/PatchworkProtocolAssigner.sol"; import "./nfts/TestPatchworkNFT.sol"; contract PatchworkNFTTest is Test { @@ -26,7 +27,7 @@ contract PatchworkNFTTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(_patchworkOwner); + _prot = new PatchworkProtocol(_patchworkOwner, address(new PatchworkProtocolAssigner(_patchworkOwner))); _scopeName = "testscope"; vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); diff --git a/test/PatchworkNFTReferences.t.sol b/test/PatchworkNFTReferences.t.sol index 1dd103e..2cf0053 100644 --- a/test/PatchworkNFTReferences.t.sol +++ b/test/PatchworkNFTReferences.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; +import "../src/PatchworkProtocolAssigner.sol"; import "./nfts/TestPatchLiteRefNFT.sol"; import "./nfts/TestFragmentLiteRefNFT.sol"; import "./nfts/TestBaseNFT.sol"; @@ -32,7 +33,7 @@ contract PatchworkNFTCombinedTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(_patchworkOwner); + _prot = new PatchworkProtocol(_patchworkOwner, address(new PatchworkProtocolAssigner(_patchworkOwner))); _scopeName = "testscope"; vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); diff --git a/test/PatchworkPatch.t.sol b/test/PatchworkPatch.t.sol index 8f33735..b2278c8 100644 --- a/test/PatchworkPatch.t.sol +++ b/test/PatchworkPatch.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; +import "../src/PatchworkProtocolAssigner.sol"; import "./nfts/TestPatchLiteRefNFT.sol"; import "./nfts/TestBaseNFT.sol"; import "./nfts/TestPatchFragmentNFT.sol"; @@ -28,8 +29,8 @@ contract PatchworkPatchTest is Test { _user2Address = address(550001); _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; - vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(_patchworkOwner); + PatchworkProtocolAssigner assigner = new PatchworkProtocolAssigner(_patchworkOwner); + _prot = new PatchworkProtocol(_patchworkOwner, address(assigner)); _scopeName = "testscope"; vm.startPrank(_scopeOwner); _prot.claimScope(_scopeName); diff --git a/test/PatchworkProtocol.t.sol b/test/PatchworkProtocol.t.sol index 6f6e172..a867a60 100644 --- a/test/PatchworkProtocol.t.sol +++ b/test/PatchworkProtocol.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; +import "../src/PatchworkProtocolAssigner.sol"; import "./nfts/TestPatchLiteRefNFT.sol"; import "./nfts/TestFragmentLiteRefNFT.sol"; import "./nfts/TestBaseNFT.sol"; @@ -37,7 +38,7 @@ contract PatchworkProtocolTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(_patchworkOwner); + _prot = new PatchworkProtocol(_patchworkOwner, address(new PatchworkProtocolAssigner(_patchworkOwner))); _scopeName = "testscope"; vm.prank(_userAddress); diff --git a/test/PatchworkUtils.t.sol b/test/PatchworkUtils.t.sol index dd1c98b..01bddd3 100644 --- a/test/PatchworkUtils.t.sol +++ b/test/PatchworkUtils.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; diff --git a/test/TestDynamicArrayLiteRefNFT.t.sol b/test/TestDynamicArrayLiteRefNFT.t.sol index a7a2db5..f817226 100644 --- a/test/TestDynamicArrayLiteRefNFT.t.sol +++ b/test/TestDynamicArrayLiteRefNFT.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; import "../src/PatchworkProtocol.sol"; +import "../src/PatchworkProtocolAssigner.sol"; import "./nfts/TestDynamicArrayLiteRefNFT.sol"; contract PatchworkAccountPatchTest is Test { @@ -26,7 +27,7 @@ contract PatchworkAccountPatchTest is Test { _scopeOwner = 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5; vm.prank(_patchworkOwner); - _prot = new PatchworkProtocol(_patchworkOwner); + _prot = new PatchworkProtocol(_patchworkOwner, address(new PatchworkProtocolAssigner(_patchworkOwner))); vm.startPrank(_scopeOwner); _scopeName = "testscope"; diff --git a/test/TestPatchLiteRefNFT.t.sol b/test/TestPatchLiteRefNFT.t.sol index 5ac29c9..0d0c207 100644 --- a/test/TestPatchLiteRefNFT.t.sol +++ b/test/TestPatchLiteRefNFT.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "forge-std/Test.sol"; import "forge-std/console.sol"; diff --git a/test/nfts/Test1155PatchNFT.sol b/test/nfts/Test1155PatchNFT.sol index 5350a70..e7b30fb 100644 --- a/test/nfts/Test1155PatchNFT.sol +++ b/test/nfts/Test1155PatchNFT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "../../src/Patchwork1155Patch.sol"; diff --git a/test/nfts/TestAccountPatchNFT.sol b/test/nfts/TestAccountPatchNFT.sol index 6200bba..9488560 100644 --- a/test/nfts/TestAccountPatchNFT.sol +++ b/test/nfts/TestAccountPatchNFT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "../../src/PatchworkAccountPatch.sol"; diff --git a/test/nfts/TestBase1155.sol b/test/nfts/TestBase1155.sol index 97b791a..2846b22 100644 --- a/test/nfts/TestBase1155.sol +++ b/test/nfts/TestBase1155.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; diff --git a/test/nfts/TestBaseNFT.sol b/test/nfts/TestBaseNFT.sol index 5db947d..cfbeb30 100644 --- a/test/nfts/TestBaseNFT.sol +++ b/test/nfts/TestBaseNFT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; diff --git a/test/nfts/TestDynamicArrayLiteRefNFT.sol b/test/nfts/TestDynamicArrayLiteRefNFT.sol index 4177c00..10e00e2 100644 --- a/test/nfts/TestDynamicArrayLiteRefNFT.sol +++ b/test/nfts/TestDynamicArrayLiteRefNFT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; /* Prototype - Generated Patchwork Meta contract for Totem NFT. diff --git a/test/nfts/TestFragmentLiteRefNFT.sol b/test/nfts/TestFragmentLiteRefNFT.sol index 877fe8c..3b5aebd 100644 --- a/test/nfts/TestFragmentLiteRefNFT.sol +++ b/test/nfts/TestFragmentLiteRefNFT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; /* Prototype - Generated Patchwork Meta contract for Totem NFT. diff --git a/test/nfts/TestFragmentSingleNFT.sol b/test/nfts/TestFragmentSingleNFT.sol index 9c7440d..ef5d717 100644 --- a/test/nfts/TestFragmentSingleNFT.sol +++ b/test/nfts/TestFragmentSingleNFT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; /* Prototype - Generated Patchwork Meta contract for Totem NFT. diff --git a/test/nfts/TestMultiFragmentNFT.sol b/test/nfts/TestMultiFragmentNFT.sol index 5c126eb..97597e1 100644 --- a/test/nfts/TestMultiFragmentNFT.sol +++ b/test/nfts/TestMultiFragmentNFT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "../../src/PatchworkFragmentMulti.sol"; import "../../src/PatchworkLiteRef.sol"; diff --git a/test/nfts/TestPatchFragmentNFT.sol b/test/nfts/TestPatchFragmentNFT.sol index 6bfa371..68474f5 100644 --- a/test/nfts/TestPatchFragmentNFT.sol +++ b/test/nfts/TestPatchFragmentNFT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; /* Prototype - Generated Patchwork Meta contract for Totem NFT. diff --git a/test/nfts/TestPatchLiteRefNFT.sol b/test/nfts/TestPatchLiteRefNFT.sol index 3fcb8b2..f334463 100644 --- a/test/nfts/TestPatchLiteRefNFT.sol +++ b/test/nfts/TestPatchLiteRefNFT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; /* Prototype - Generated Patchwork Meta contract for Totem NFT. diff --git a/test/nfts/TestPatchNFT.sol b/test/nfts/TestPatchNFT.sol index aa83085..2633110 100644 --- a/test/nfts/TestPatchNFT.sol +++ b/test/nfts/TestPatchNFT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; /* Prototype - Generated Patchwork Meta contract for Totem NFT. diff --git a/test/nfts/TestPatchworkNFT.sol b/test/nfts/TestPatchworkNFT.sol index 8584023..4831d97 100644 --- a/test/nfts/TestPatchworkNFT.sol +++ b/test/nfts/TestPatchworkNFT.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.23; import "../../src/Patchwork721.sol"; import "../../src/interfaces/IPatchworkMintable.sol"; From 4549d8299cef057e966f776e45334dbb49dac0ed Mon Sep 17 00:00:00 2001 From: Rob Green Date: Fri, 26 Jan 2024 14:08:52 -0800 Subject: [PATCH 61/63] Deploy and Verify using create2 (#87) * deploy stub * deploy + verify * verify wip * enoceded constructor args * constructor args * manual verificatin steps * Small trial and error updates * Working completely with new delegate * removed file that shouldn't have been there --------- Co-authored-by: Kevin Day --- .gitignore | 2 + management/.env.example | 11 +++++ management/README.md | 23 +++++++++ management/constructor-args.txt | 1 + management/deploy.s.sol | 33 +++++++++++++ management/deploy.sh | 51 ++++++++++++++++++++ management/encodeConstructorArgs.s.sol | 13 ++++++ management/verify.sh | 65 ++++++++++++++++++++++++++ remappings.txt | 1 - 9 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 management/.env.example create mode 100644 management/README.md create mode 100644 management/constructor-args.txt create mode 100644 management/deploy.s.sol create mode 100755 management/deploy.sh create mode 100644 management/encodeConstructorArgs.s.sol create mode 100755 management/verify.sh diff --git a/.gitignore b/.gitignore index 14e0bf6..5eb000f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ out/ !/broadcast /broadcast/*/31337/ /broadcast/**/dry-run/ +**/broadcast/** + # Docs docs/ diff --git a/management/.env.example b/management/.env.example new file mode 100644 index 0000000..0761af8 --- /dev/null +++ b/management/.env.example @@ -0,0 +1,11 @@ +# .env file +PRIVATE_KEY=your_private_key_here +BASE_RPC_URL=your_base_network_rpc_url +BASE_PATCHWORK_OWNER=base_patchwork_owner_address +BASE_PATCHWORK_ADDRESS=base_patchwork_address +BASE_CHAIN_ID=8453 +SEPOLIA_RPC_URL=your_sepolia_network_rpc_url +SEPOLIA_PATCHWORK_OWNER=sepolia_patchwork_owner_address +SEPOLIA_CHAIN_ID=11155111 +SEPOLIA_PATCHWORK_ADDRESS=0x635BDfB811Ef377a759231Ac3A2746814A93a7B8 + diff --git a/management/README.md b/management/README.md new file mode 100644 index 0000000..f0031dc --- /dev/null +++ b/management/README.md @@ -0,0 +1,23 @@ +Create a .env file in the format of .env.example + +To deoploy on sepolia: +./deploy.sh sepolia + +To deploy on base: +./deploy.sh base + +Include --broadcast when you are ready to deploy + +To verify on ether scan: + +./verify.sh base + + or + + ./verify.sh sepolia + +manual verification: + +forge verify-contract 0x635BDfB811Ef377a759231Ac3A2746814A93a7B8 src/PatchworkProtocol.sol:PatchworkProtocol --optimizer-runs 200 --constructor-args "0x0000000000000000000000007239aec2fa59303ba68bece386be2a9ddc72e63b" --show-standard-json-input > etherscan.json +patch manually etherscan.json : "optimizer":{"enabled":true,"runs":100} -> "optimizer":{"enabled":true,"runs":100},"viaIR":true (or something of that sort) +upload json to etherscan manually \ No newline at end of file diff --git a/management/constructor-args.txt b/management/constructor-args.txt new file mode 100644 index 0000000..3ea0442 --- /dev/null +++ b/management/constructor-args.txt @@ -0,0 +1 @@ +0x7239aEc2fA59303BA68BEcE386BE2A9dDC72e63B \ No newline at end of file diff --git a/management/deploy.s.sol b/management/deploy.s.sol new file mode 100644 index 0000000..fea752c --- /dev/null +++ b/management/deploy.s.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import { Script } from "forge-std/Script.sol"; +import "forge-std/console.sol"; +import { PatchworkProtocol } from "../src/PatchworkProtocol.sol"; +import { PatchworkProtocolAssigner } from "../src/PatchworkProtocolAssigner.sol"; + +contract DeterministicPatchworkDeploy is Script { + + address internal constant _DETERMINISTIC_CREATE2_FACTORY = 0x7A0D94F55792C434d74a40883C6ed8545E406D12; + + function run() public { + address patchworkOwner = vm.envAddress("PATCHWORK_OWNER"); + vm.startBroadcast(); + + bytes memory creationCode = type(PatchworkProtocolAssigner).creationCode; + bytes memory creationBytecode = abi.encodePacked(creationCode, abi.encode(patchworkOwner)); + (bool success, bytes memory returnData) = _DETERMINISTIC_CREATE2_FACTORY.call(creationBytecode); + require(success, "Failed to deploy Assigner Module"); + address assignerAddress = address(uint160(bytes20(returnData))); + console.log("Deployed Assigner module contract at: ", assignerAddress); + + creationCode = type(PatchworkProtocol).creationCode; + creationBytecode = abi.encodePacked(creationCode, abi.encode(patchworkOwner), abi.encode(assignerAddress)); + (success, returnData) = _DETERMINISTIC_CREATE2_FACTORY.call(creationBytecode); + require(success, "Failed to deploy Patchwork"); + address patchworkAddress = address(uint160(bytes20(returnData))); + console.log("Deployed Patchwork contract at: ", patchworkAddress); + + vm.stopBroadcast(); + } +} diff --git a/management/deploy.sh b/management/deploy.sh new file mode 100755 index 0000000..8e8b36e --- /dev/null +++ b/management/deploy.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# deploy.sh + +# Usage instructions +if [ $# -lt 1 ]; then + echo "Usage: $0 [--broadcast]" + echo "Supported networks: base, sepolia" + exit 1 +fi + +# Load the .env file +if [ -f .env ]; then + export $(grep -v '^#' .env | xargs) +else + echo ".env file not found" + exit 1 +fi + +NETWORK=$1 +PATCHWORK_OWNER="" +RPC_URL="" + +case $NETWORK in + "base") + PATCHWORK_OWNER=$BASE_PATCHWORK_OWNER + RPC_URL=$BASE_RPC_URL + ;; + "sepolia") + PATCHWORK_OWNER=$SEPOLIA_PATCHWORK_OWNER + RPC_URL=$SEPOLIA_RPC_URL + ;; + *) + echo "Network not supported" + exit 1 + ;; +esac + +# Check for broadcast flag +if [[ " $* " =~ " --broadcast " ]]; then + echo "Broadcasting is enabled" + forge_options="$forge_options --broadcast" +fi + +# Export the owner address as an environment variable +export PATCHWORK_OWNER + +# Execute the Solidity script with the environment variable +forge script $forge_options --optimize --optimizer-runs 200 ./deploy.s.sol:DeterministicPatchworkDeploy \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY diff --git a/management/encodeConstructorArgs.s.sol b/management/encodeConstructorArgs.s.sol new file mode 100644 index 0000000..f235e0a --- /dev/null +++ b/management/encodeConstructorArgs.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import { PatchworkProtocol } from "src/PatchworkProtocol.sol"; + +contract EncodeConstructorArgs is Script { + function run() public { + address patchworkOwner = 0x7239aEc2fA59303BA68BEcE386BE2A9dDC72e63B; + bytes memory encodedArgs = abi.encode(patchworkOwner); + console.log("Encoded Constructor Args:", vm.toString(encodedArgs)); + } +} diff --git a/management/verify.sh b/management/verify.sh new file mode 100755 index 0000000..134354c --- /dev/null +++ b/management/verify.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# verify.sh + +# Usage instructions +if [ $# -lt 1 ]; then + echo "Usage: $0 " + echo "Supported networks: base, sepolia" + exit 1 +fi + +NETWORK=$1 + +# Load the .env file +if [ -f .env ]; then + export $(grep -v '^#' .env | xargs) +else + echo ".env file not found" + exit 1 +fi + +# Set the network-specific contract address and chain ID +CONTRACT_ADDRESS="" +ASSIGNER_CONTRACT_ADDRESS="" +CHAIN_ID="" + +case $NETWORK in + "base") + CONTRACT_ADDRESS=$BASE_PATCHWORK_ADDRESS + ASSIGNER_CONTRACT_ADDRESS=$BASE_PATCHWORK_ASSIGNER_ADDRESS + CHAIN_ID=$BASE_CHAIN_ID + ;; + "sepolia") + CONTRACT_ADDRESS=$SEPOLIA_PATCHWORK_ADDRESS + ASSIGNER_CONTRACT_ADDRESS=$SEPOLIA_PATCHWORK_ASSIGNER_ADDRESS + CHAIN_ID=$SEPOLIA_CHAIN_ID + ;; + *) + echo "Network not supported" + exit 1 + ;; +esac + + +COMPILER_VERSION="0.8.23+commit.f704f362" +OPTIMIZER_RUNS=200 + +forge verify-contract $ASSIGNER_CONTRACT_ADDRESS src/PatchworkProtocolAssigner.sol:PatchworkProtocolAssigner \ + --constructor-args "0x0000000000000000000000007239aec2fa59303ba68bece386be2a9ddc72e63b" \ + --chain-id $CHAIN_ID \ + --compiler-version $COMPILER_VERSION \ + --optimizer-runs $OPTIMIZER_RUNS \ + --etherscan-api-key $ETHERSCAN_API_KEY \ + --watch + +CLEANED_ASSIGNER_CONTRACT_ADDRESS=$(echo "$ASSIGNER_CONTRACT_ADDRESS" | sed 's/^0x//') + +forge verify-contract $CONTRACT_ADDRESS src/PatchworkProtocol.sol:PatchworkProtocol \ + --constructor-args "0x0000000000000000000000007239aec2fa59303ba68bece386be2a9ddc72e63b000000000000000000000000$CLEANED_ASSIGNER_CONTRACT_ADDRESS"\ + --chain-id $CHAIN_ID \ + --compiler-version $COMPILER_VERSION \ + --optimizer-runs $OPTIMIZER_RUNS \ + --etherscan-api-key $ETHERSCAN_API_KEY \ + --watch + diff --git a/remappings.txt b/remappings.txt index fd818cf..90b2e67 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,2 @@ @openzeppelin/=lib/openzeppelin-contracts/ forge-std/=lib/forge-std/src/ - From 3f52cea9278f9e8bd90109b3d1474f1544334b01 Mon Sep 17 00:00:00 2001 From: Rob Green Date: Wed, 31 Jan 2024 20:42:31 -0800 Subject: [PATCH 62/63] Fixed fee handling issue with batch assign (#89) --- src/PatchworkProtocolAssigner.sol | 55 ++++++++++++++++++++++--------- test/Fees.t.sol | 37 +++++++++++++++++++++ 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/src/PatchworkProtocolAssigner.sol b/src/PatchworkProtocolAssigner.sol index 0c5416b..9d910ce 100644 --- a/src/PatchworkProtocolAssigner.sol +++ b/src/PatchworkProtocolAssigner.sol @@ -35,13 +35,16 @@ contract PatchworkProtocolAssigner is PatchworkProtocolCommon { constructor(address owner_) PatchworkProtocolCommon(owner_) {} - // common to assigns - function _handleAssignFee(string memory scopeName, IPatchworkProtocol.Scope storage scope, address fragmentAddress) private returns (uint256 scopeFee, uint256 protocolFee) { + /** + @dev common to assigns + @dev fees are processed per-assignment + */ + function _handleAssignFee(uint256 value, string memory scopeName, IPatchworkProtocol.Scope storage scope, address fragmentAddress) private returns (uint256 scopeFee, uint256 protocolFee, uint256 valueRemaining) { uint256 assignFee = scope.assignFees[fragmentAddress]; - if (msg.value != assignFee) { + if (value < assignFee) { revert IPatchworkProtocol.IncorrectFeeAmount(); } - if (msg.value > 0) { + if (value > 0) { uint256 assignBp; IPatchworkProtocol.FeeConfigOverride storage feeOverride = _scopeFeeOverrides[scopeName]; if (feeOverride.active) { @@ -49,10 +52,11 @@ contract PatchworkProtocolAssigner is PatchworkProtocolCommon { } else { assignBp = _protocolFeeConfig.assignBp; } - protocolFee = msg.value * assignBp / _FEE_BASIS_DENOM; - scopeFee = msg.value - protocolFee; + protocolFee = assignFee * assignBp / _FEE_BASIS_DENOM; + scopeFee = assignFee - protocolFee; _protocolBalance += protocolFee; scope.balance += scopeFee; + valueRemaining = value - assignFee; } } @@ -61,7 +65,10 @@ contract PatchworkProtocolAssigner is PatchworkProtocolCommon { */ function assign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId) public payable mustNotBeFrozen(target, targetTokenId) { address targetOwner = IERC721(target).ownerOf(targetTokenId); - uint64 ref = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); + (uint64 ref, uint256 valueRemaining) = _doAssign(msg.value, fragment, fragmentTokenId, target, targetTokenId, targetOwner); + if (valueRemaining > 0) { + revert IPatchworkProtocol.IncorrectFeeAmount(); + } IPatchworkLiteRef(target).addReference(targetTokenId, ref); } @@ -70,7 +77,10 @@ contract PatchworkProtocolAssigner is PatchworkProtocolCommon { */ function assign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, uint256 targetMetadataId) public payable mustNotBeFrozen(target, targetTokenId) { address targetOwner = IERC721(target).ownerOf(targetTokenId); - uint64 ref = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); + (uint64 ref, uint256 valueRemaining) = _doAssign(msg.value, fragment, fragmentTokenId, target, targetTokenId, targetOwner); + if (valueRemaining > 0) { + revert IPatchworkProtocol.IncorrectFeeAmount(); + } IPatchworkLiteRef(target).addReference(targetTokenId, ref, targetMetadataId); } @@ -99,44 +109,57 @@ contract PatchworkProtocolAssigner is PatchworkProtocolCommon { } targetOwner = IERC721(target).ownerOf(targetTokenId); refs = new uint64[](fragments.length); + uint256 value = msg.value; for (uint i = 0; i < fragments.length; i++) { address fragment = fragments[i]; uint256 fragmentTokenId = tokenIds[i]; - refs[i] = _doAssign(fragment, fragmentTokenId, target, targetTokenId, targetOwner); + (refs[i], value) = _doAssign(value, fragment, fragmentTokenId, target, targetTokenId, targetOwner); + } + // If the correct fee amount is provided there should be no remainder after all assignments are processed + if (value > 0) { + revert IPatchworkProtocol.IncorrectFeeAmount(); } } /** @notice Performs assignment of an IPatchworkAssignable to an IPatchworkLiteRef + @param value the remaining message value after any previous assignments in this tx @param fragment the IPatchworkAssignable's address @param fragmentTokenId the IPatchworkAssignable's tokenId @param target the IPatchworkLiteRef target's address @param targetTokenId the IPatchworkLiteRef target's tokenId @param targetOwner the owner address of the target - @return uint64 literef of assignable in target + @return ref literef of assignable in target + @return valueRemaining message value remaining after fee */ - function _doAssign(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, address targetOwner) private mustNotBeFrozen(fragment, fragmentTokenId) returns (uint64) { + function _doAssign(uint256 value, address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, address targetOwner) private mustNotBeFrozen(fragment, fragmentTokenId) returns (uint64 ref, uint256 valueRemaining) { if (fragment == target && fragmentTokenId == targetTokenId) { revert IPatchworkProtocol.SelfAssignmentNotAllowed(fragment, fragmentTokenId); } + uint256 scopeFee; + uint256 protocolFee; // Use the target's scope for general permission and check the fragment for detailed permissions - (uint256 scopeFee, uint256 protocolFee) = _doAssignPermissionsAndFees(fragment, fragmentTokenId, target, targetTokenId, targetOwner); + (scopeFee, protocolFee, valueRemaining) = _doAssignPermissionsAndFees(value, fragment, fragmentTokenId, target, targetTokenId, targetOwner); // Handle storage and duplicate checks - uint64 ref = _doAssignStorageAndDupes(fragment, fragmentTokenId, target, targetTokenId); + ref = _doAssignStorageAndDupes(fragment, fragmentTokenId, target, targetTokenId); // these two end up beyond stack depth on some compiler settings. emit IPatchworkProtocol.Assign(targetOwner, fragment, fragmentTokenId, target, targetTokenId, scopeFee, protocolFee); - return ref; + return (ref, valueRemaining); } /** @notice Handles assignment permissions and fees + @param value the remaining message value after any previous assignments in this tx @param fragment the IPatchworkAssignable's address @param fragmentTokenId the IPatchworkAssignable's tokenId @param target the IPatchworkLiteRef target's address @param targetTokenId the IPatchworkLiteRef target's tokenId @param targetOwner the owner address of the target + @return scopeFee the scope fee taken + @return protocolFee the protocol fee taken + @return valueRemaining the remaining message value after fees taken */ - function _doAssignPermissionsAndFees(address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, address targetOwner) private returns (uint256 scopeFee, uint256 protocolFee) { + function _doAssignPermissionsAndFees(uint256 value, address fragment, uint256 fragmentTokenId, address target, uint256 targetTokenId, address targetOwner) private returns (uint256 scopeFee, uint256 protocolFee, uint256 valueRemaining) { string memory targetScopeName = _getScopeName(target); if (!IPatchworkAssignable(fragment).allowAssignment(fragmentTokenId, target, targetTokenId, targetOwner, msg.sender, targetScopeName)) { revert IPatchworkProtocol.NotAuthorized(msg.sender); @@ -160,7 +183,7 @@ contract PatchworkProtocolAssigner is PatchworkProtocolCommon { string memory fragmentScopeName = _getScopeName(fragment); IPatchworkProtocol.Scope storage fragmentScope = _mustHaveScope(fragmentScopeName); _mustBeWhitelisted(fragmentScopeName, fragmentScope, fragment); - (scopeFee, protocolFee) = _handleAssignFee(fragmentScopeName, fragmentScope, fragment); + (scopeFee, protocolFee, valueRemaining) = _handleAssignFee(value, fragmentScopeName, fragmentScope, fragment); } /** diff --git a/test/Fees.t.sol b/test/Fees.t.sol index 3fead76..9c748e0 100644 --- a/test/Fees.t.sol +++ b/test/Fees.t.sol @@ -463,5 +463,42 @@ contract FeesTest is Test { _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(0, 3001, 0, true)); vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.InvalidFeeValue.selector)); _prot.proposeScopeFeeOverride(_scopeName, IPatchworkProtocol.FeeConfigOverride(0, 0, 3001, true)); + } + + function testAssignBatchFees() public { + vm.startPrank(_scopeOwner); + _prot.setScopeRules(_scopeName, false, false, true); + TestFragmentLiteRefNFT nft = new TestFragmentLiteRefNFT(address(_prot)); + nft.registerReferenceAddress(address(nft)); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotWhitelisted.selector, _scopeName, address(nft))); + _prot.setAssignFee(address(nft), 1); + _prot.addWhitelist(_scopeName, address(nft)); + vm.stopPrank(); + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.NotAuthorized.selector, _userAddress)); + vm.prank(_userAddress); + _prot.setAssignFee(address(nft), 1); + // success + vm.startPrank(_scopeOwner); + _prot.setAssignFee(address(nft), 1); + uint256 nftAssignFee = _prot.getAssignFee(address(nft)); + assertEq(1, nftAssignFee); + uint256 n1 = nft.mint(_userAddress, ""); + uint256[] memory fragmentIds = new uint256[](8); + address[] memory fragmentAddresses = new address[](8); + for (uint8 i = 0; i < 8; i++) { + fragmentAddresses[i] = address(nft); + fragmentIds[i] = nft.mint(_userAddress, ""); } + // No fee given should revert + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.assignBatch(fragmentAddresses, fragmentIds, address(nft), n1); + // too little fee should revert + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.assignBatch{value: nftAssignFee}(fragmentAddresses, fragmentIds, address(nft), n1); + // too much fee should revert + vm.expectRevert(abi.encodeWithSelector(IPatchworkProtocol.IncorrectFeeAmount.selector)); + _prot.assignBatch{value: nftAssignFee * 9}(fragmentAddresses, fragmentIds, address(nft), n1); + // correct fee should pass + _prot.assignBatch{value: nftAssignFee * 8}(fragmentAddresses, fragmentIds, address(nft), n1); + } } \ No newline at end of file From 4e0efdb3b47c57bb29d23ee985359e31b200dbcb Mon Sep 17 00:00:00 2001 From: donaldinho Date: Thu, 1 Feb 2024 17:08:08 +0000 Subject: [PATCH 63/63] Add base sepolia and deploy latest to testnets --- management/.env.example | 7 +++++-- management/deploy.sh | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/management/.env.example b/management/.env.example index 0761af8..abf88fd 100644 --- a/management/.env.example +++ b/management/.env.example @@ -7,5 +7,8 @@ BASE_CHAIN_ID=8453 SEPOLIA_RPC_URL=your_sepolia_network_rpc_url SEPOLIA_PATCHWORK_OWNER=sepolia_patchwork_owner_address SEPOLIA_CHAIN_ID=11155111 -SEPOLIA_PATCHWORK_ADDRESS=0x635BDfB811Ef377a759231Ac3A2746814A93a7B8 - +SEPOLIA_PATCHWORK_ADDRESS=0xeAB0ceC50d344c1256fB983fee6f87D6A040d329 +BASE_SEPOLIA_RPC_URL=https://base-sepolia.g.alchemy.com/v2/6p1q3ciiOzYUwMaNdysskDHdLLV56sGx +BASE_SEPOLIA_PATCHWORK_OWNER=0x435498Afe7E9b01f92D673fADA429fAeA08870aA +BASE_SEPOLIA_CHAIN_ID=11155111 +BASE_SEPOLIA_PATCHWORK_ADDRESS=0xeAB0ceC50d344c1256fB983fee6f87D6A040d329 diff --git a/management/deploy.sh b/management/deploy.sh index 8e8b36e..bcff986 100755 --- a/management/deploy.sh +++ b/management/deploy.sh @@ -30,6 +30,10 @@ case $NETWORK in PATCHWORK_OWNER=$SEPOLIA_PATCHWORK_OWNER RPC_URL=$SEPOLIA_RPC_URL ;; + "base-sepolia") + PATCHWORK_OWNER=$BASE_SEPOLIA_PATCHWORK_OWNER + RPC_URL=$BASE_SEPOLIA_RPC_URL + ;; *) echo "Network not supported" exit 1