diff --git a/src/ShipIt.sol b/src/ShipIt.sol index 46c6c82..a249b0c 100644 --- a/src/ShipIt.sol +++ b/src/ShipIt.sol @@ -10,22 +10,16 @@ contract ShipIt is Ownable { mapping(address => address) public addressVault; // users can store their personal vaults for ease of use uint256 public usageFee = .00015 ether; // charge a small fee for the cost savings it provides - event TokenTransfer(address indexed contractAddress, uint256 tokenIndex, address indexed from, address indexed to); - /************************* Modifiers **************************/ - modifier onlyIfTokenOwner( - address contractAddress, - uint256 tokenIndex, - bool isERC1155 + modifier onlyIfPaid( + address[] calldata recipients, + uint256[] calldata tokenIndexes ) { - if (isERC1155) { - require(ERC1155(contractAddress).balanceOf(msg.sender, tokenIndex) > 0, "You must own the token."); - } else { - require(msg.sender == ERC721(contractAddress).ownerOf(tokenIndex), "You must own the token."); - } + require(tokenIndexes.length == recipients.length, "Array lengths must match."); + require(msg.value >= tokenIndexes.length * usageFee, "Invalid usage fee sent."); _; } @@ -50,34 +44,31 @@ contract ShipIt is Ownable { addressVault[msg.sender] = vaultAddress; } - function contractTransfer( + // Expects flat list of recipients and token IDs + function erc721BulkTransfer( address contractAddress, - uint256 tokenIndex, - address recipient, - bool isERC1155 - ) private { - if (isERC1155) { - require(ERC1155(contractAddress).balanceOf(msg.sender, tokenIndex) > 0, "Sender is not the token owner, cannot proceed with transfer."); - require(ERC1155(contractAddress).isApprovedForAll(msg.sender, address(this)), "Contract not approved to send tokens on Sender behalf."); - ERC1155(contractAddress).safeTransferFrom(msg.sender, recipient, tokenIndex, 1, bytes("")); - } else { - require(msg.sender == ERC721(contractAddress).ownerOf(tokenIndex), "Sender is not the token owner, cannot proceed with transfer."); - require(ERC721(contractAddress).isApprovedForAll(msg.sender, address(this)), "Contract not approved to send tokens on Sender behalf."); - ERC721(contractAddress).safeTransferFrom(msg.sender, recipient, tokenIndex); + address[] calldata recipients, + uint256[] calldata tokenIndexes + ) external payable onlyIfPaid(recipients, tokenIndexes) { + require(ERC721(contractAddress).isApprovedForAll(msg.sender, address(this)), "Contract not approved to send tokens on Sender behalf."); + for(uint256 i; i < tokenIndexes.length; i++) { + require(msg.sender == ERC721(contractAddress).ownerOf(tokenIndexes[i]), "Sender is not the token owner, cannot proceed with transfer."); + ERC721(contractAddress).safeTransferFrom(msg.sender, recipients[i], tokenIndexes[i]); } - emit TokenTransfer(contractAddress, tokenIndex, msg.sender, recipient); } - function contractBulkTransfer( + // Expects a tally of ERC-1155 token amounts batched beforehand for simple sending + function erc1155BulkTransfer( address contractAddress, - uint256[] calldata tokenIndexes, address[] calldata recipients, - bool isERC1155 - ) external payable { - require(tokenIndexes.length == recipients.length, "Array lengths must match."); - require(msg.value >= tokenIndexes.length * usageFee, "Invalid usage fee sent."); + uint256[] calldata tokenIndexes, + uint256[] calldata amounts + ) external payable onlyIfPaid(recipients, tokenIndexes) { + require(amounts.length == recipients.length, "Array lengths must match."); + require(ERC1155(contractAddress).isApprovedForAll(msg.sender, address(this)), "Contract not approved to send tokens on Sender behalf."); for(uint256 i; i < tokenIndexes.length; i++) { - contractTransfer(contractAddress, tokenIndexes[i], recipients[i], isERC1155); + require(ERC1155(contractAddress).balanceOf(msg.sender, tokenIndexes[i]) >= amounts[i], "Not enough balance owned of the given token ID."); + ERC1155(contractAddress).safeTransferFrom(msg.sender, recipients[i], tokenIndexes[i], amounts[i], bytes("")); } } diff --git a/test/ShipIt.t.sol b/test/ShipIt.t.sol index 7d290b2..9e9116d 100644 --- a/test/ShipIt.t.sol +++ b/test/ShipIt.t.sol @@ -34,11 +34,10 @@ contract ShipItTest is Test { vm.startPrank(address(5)); nft721.mint(amt); nft721.setApprovalForAll(address(shipit), true); - shipit.contractBulkTransfer{value: val}( + shipit.erc721BulkTransfer{value: val}( address(nft721), - tokenIndexes, recipients, - false + tokenIndexes ); // assert balances } @@ -58,11 +57,10 @@ contract ShipItTest is Test { nft721.setApprovalForAll(address(shipit), true); vm.stopPrank(); vm.prank(address(3)); - shipit.contractBulkTransfer{value: val}( + shipit.erc721BulkTransfer{value: val}( address(nft721), - tokenIndexes, recipients, - false + tokenIndexes ); } @@ -70,33 +68,40 @@ contract ShipItTest is Test { uint256 amt = 10; uint256 fee = shipit.usageFee(); uint256 val = fee * amt; - uint256[] memory tokenIndexes = new uint256[](amt); - address[] memory recipients = new address[](amt); - for (uint256 i; i < amt; i++) { - tokenIndexes[i] = 1; - recipients[i] = address(1); - } - vm.deal(address(5), 1 ether); - vm.startPrank(address(5)); + address[] memory recipients = new address[](2); + uint256[] memory tokenIndexes = new uint256[](2); + uint256[] memory amounts = new uint256[](2); + recipients[0] = address(11); + recipients[1] = address(11); + tokenIndexes[0] = 1; + tokenIndexes[1] = 5; + amounts[0] = amt; + amounts[1] = amt; + vm.deal(address(50), 1 ether); + vm.startPrank(address(50)); nft1155.mint(1, amt); + nft1155.mint(5, amt); nft1155.setApprovalForAll(address(shipit), true); - shipit.contractBulkTransfer{value: val}( + shipit.erc1155BulkTransfer{value: val}( address(nft1155), - tokenIndexes, recipients, - true + tokenIndexes, + amounts ); - assertEq(nft1155.balanceOf(address(1), 1), 10); + assertEq(nft1155.balanceOf(address(11), 1), 10); + assertEq(nft1155.balanceOf(address(11), 5), 10); } function testFail1155NonTokenOwnerCanSend() public { uint256 amt = 1; uint256 fee = shipit.usageFee(); uint256 val = fee * amt; - uint256[] memory tokenIndexes = new uint256[](amt); address[] memory recipients = new address[](amt); + uint256[] memory tokenIndexes = new uint256[](amt); + uint256[] memory amounts = new uint256[](amt); tokenIndexes[0] = 1; recipients[0] = address(1); + amounts[0] = amt; vm.deal(address(5), 1 ether); vm.deal(address(3), 1 ether); vm.startPrank(address(5)); @@ -104,11 +109,11 @@ contract ShipItTest is Test { nft1155.setApprovalForAll(address(shipit), true); vm.stopPrank(); vm.prank(address(3)); - shipit.contractBulkTransfer{value: val}( + shipit.erc1155BulkTransfer{value: val}( address(nft1155), - tokenIndexes, recipients, - true + tokenIndexes, + amounts ); }