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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/IPatchworkLiteRef.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions src/IPatchworkProtocol.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
102 changes: 90 additions & 12 deletions src/PatchworkProtocol.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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();
}
Expand All @@ -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);
}

Expand All @@ -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
Expand All @@ -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);
}

Expand Down
14 changes: 8 additions & 6 deletions test/PatchworkProtocol.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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]);
Expand Down
5 changes: 5 additions & 0 deletions test/TestDynamicArrayLiteRefNFT.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,10 @@ contract PatchworkAccountPatchTest is Test {
}
}

function testUnusedFuncs() public {
TestDynamicArrayLiteRefNFT nft = new TestDynamicArrayLiteRefNFT(address(_prot));
nft.loadAllStaticReferences(0);
}


}
5 changes: 5 additions & 0 deletions test/TestPatchLiteRefNFT.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading