From 97037a6cdff27b0e8f02d155052cba6a39bdcf8e Mon Sep 17 00:00:00 2001 From: aapsi Date: Sun, 2 Nov 2025 12:20:40 +0530 Subject: [PATCH 1/4] test: add ERC1155 test harnesses and receiver mock --- .../ERC1155/harnesses/ERC1155FacetHarness.sol | 99 +++++++++++++++++++ .../ERC1155/harnesses/LibERC1155Harness.sol | 96 ++++++++++++++++++ .../ERC1155/mocks/ERC1155ReceiverMock.sol | 76 ++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 test/token/ERC1155/ERC1155/harnesses/ERC1155FacetHarness.sol create mode 100644 test/token/ERC1155/ERC1155/harnesses/LibERC1155Harness.sol create mode 100644 test/token/ERC1155/ERC1155/mocks/ERC1155ReceiverMock.sol diff --git a/test/token/ERC1155/ERC1155/harnesses/ERC1155FacetHarness.sol b/test/token/ERC1155/ERC1155/harnesses/ERC1155FacetHarness.sol new file mode 100644 index 00000000..de33d594 --- /dev/null +++ b/test/token/ERC1155/ERC1155/harnesses/ERC1155FacetHarness.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {ERC1155Facet} from "../../../../../src/token/ERC1155/ERC1155Facet.sol"; + +/// @title ERC1155FacetHarness +/// @notice Test harness for ERC1155Facet that adds initialization and minting for testing +contract ERC1155FacetHarness is ERC1155Facet { + /// @notice Initialize the ERC1155 storage + /// @dev Only used for testing - production diamonds should initialize in constructor + function initialize(string memory _uri) external { + ERC1155Storage storage s = getStorage(); + s.uri = _uri; + } + + /// @notice Set the base URI + /// @dev Only used for testing + function setBaseURI(string memory _baseURI) external { + ERC1155Storage storage s = getStorage(); + s.baseURI = _baseURI; + } + + /// @notice Set a token-specific URI + /// @dev Only used for testing + function setTokenURI(uint256 _tokenId, string memory _tokenURI) external { + ERC1155Storage storage s = getStorage(); + s.tokenURIs[_tokenId] = _tokenURI; + string memory fullURI = bytes(_tokenURI).length > 0 ? string.concat(s.baseURI, _tokenURI) : s.uri; + emit URI(fullURI, _tokenId); + } + + /// @notice Mint tokens to an address + /// @dev Only used for testing - exposes internal mint functionality + function mint(address _to, uint256 _id, uint256 _value) external { + if (_to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + ERC1155Storage storage s = getStorage(); + s.balanceOf[_id][_to] += _value; + emit TransferSingle(msg.sender, address(0), _to, _id, _value); + } + + /// @notice Mint multiple token types to an address + /// @dev Only used for testing - exposes internal mintBatch functionality + function mintBatch(address _to, uint256[] memory _ids, uint256[] memory _values) external { + if (_to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + if (_ids.length != _values.length) { + revert ERC1155InvalidArrayLength(_ids.length, _values.length); + } + ERC1155Storage storage s = getStorage(); + for (uint256 i = 0; i < _ids.length; i++) { + s.balanceOf[_ids[i]][_to] += _values[i]; + } + emit TransferBatch(msg.sender, address(0), _to, _ids, _values); + } + + /// @notice Burn tokens from an address + /// @dev Only used for testing - exposes internal burn functionality + function burn(address _from, uint256 _id, uint256 _value) external { + if (_from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } + ERC1155Storage storage s = getStorage(); + uint256 fromBalance = s.balanceOf[_id][_from]; + if (fromBalance < _value) { + revert ERC1155InsufficientBalance(_from, fromBalance, _value, _id); + } + unchecked { + s.balanceOf[_id][_from] = fromBalance - _value; + } + emit TransferSingle(msg.sender, _from, address(0), _id, _value); + } + + /// @notice Burn multiple token types from an address + /// @dev Only used for testing - exposes internal burnBatch functionality + function burnBatch(address _from, uint256[] memory _ids, uint256[] memory _values) external { + if (_from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } + if (_ids.length != _values.length) { + revert ERC1155InvalidArrayLength(_ids.length, _values.length); + } + ERC1155Storage storage s = getStorage(); + for (uint256 i = 0; i < _ids.length; i++) { + uint256 id = _ids[i]; + uint256 value = _values[i]; + uint256 fromBalance = s.balanceOf[id][_from]; + if (fromBalance < value) { + revert ERC1155InsufficientBalance(_from, fromBalance, value, id); + } + unchecked { + s.balanceOf[id][_from] = fromBalance - value; + } + } + emit TransferBatch(msg.sender, _from, address(0), _ids, _values); + } +} diff --git a/test/token/ERC1155/ERC1155/harnesses/LibERC1155Harness.sol b/test/token/ERC1155/ERC1155/harnesses/LibERC1155Harness.sol new file mode 100644 index 00000000..a83139aa --- /dev/null +++ b/test/token/ERC1155/ERC1155/harnesses/LibERC1155Harness.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {LibERC1155} from "../../../../../src/token/ERC1155/LibERC1155.sol"; + +/// @title LibERC1155Harness +/// @notice Test harness that exposes LibERC1155's internal functions as external +/// @dev Required for testing since LibERC1155 only has internal functions +contract LibERC1155Harness { + /// @notice Initialize the ERC1155 storage + /// @dev Only used for testing + function initialize(string memory _uri) external { + LibERC1155.ERC1155Storage storage s = LibERC1155.getStorage(); + s.uri = _uri; + } + + /// @notice Set the base URI + function setBaseURI(string memory _baseURI) external { + LibERC1155.setBaseURI(_baseURI); + } + + /// @notice Set a token-specific URI + function setTokenURI(uint256 _tokenId, string memory _tokenURI) external { + LibERC1155.setTokenURI(_tokenId, _tokenURI); + } + + /// @notice Exposes LibERC1155.mint as an external function + function mint(address _to, uint256 _id, uint256 _value) external { + LibERC1155.mint(_to, _id, _value); + } + + /// @notice Exposes LibERC1155.mintBatch as an external function + function mintBatch(address _to, uint256[] memory _ids, uint256[] memory _values) external { + LibERC1155.mintBatch(_to, _ids, _values); + } + + /// @notice Exposes LibERC1155.burn as an external function + function burn(address _from, uint256 _id, uint256 _value) external { + LibERC1155.burn(_from, _id, _value); + } + + /// @notice Exposes LibERC1155.burnBatch as an external function + function burnBatch(address _from, uint256[] memory _ids, uint256[] memory _values) external { + LibERC1155.burnBatch(_from, _ids, _values); + } + + /// @notice Exposes LibERC1155.safeTransferFrom as an external function + function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value) external { + LibERC1155.safeTransferFrom(_from, _to, _id, _value, msg.sender); + } + + /// @notice Exposes LibERC1155.safeBatchTransferFrom as an external function + function safeBatchTransferFrom(address _from, address _to, uint256[] memory _ids, uint256[] memory _values) + external + { + LibERC1155.safeBatchTransferFrom(_from, _to, _ids, _values, msg.sender); + } + + /// @notice Get the URI for a token type + function uri(uint256 _id) external view returns (string memory) { + LibERC1155.ERC1155Storage storage s = LibERC1155.getStorage(); + string memory tokenURI = s.tokenURIs[_id]; + return bytes(tokenURI).length > 0 ? string.concat(s.baseURI, tokenURI) : s.uri; + } + + /// @notice Get the balance of an account for a token type + function balanceOf(address _account, uint256 _id) external view returns (uint256) { + return LibERC1155.getStorage().balanceOf[_id][_account]; + } + + /// @notice Get balances for multiple account/token pairs + function balanceOfBatch(address[] memory _accounts, uint256[] memory _ids) + external + view + returns (uint256[] memory) + { + require(_accounts.length == _ids.length, "Length mismatch"); + uint256[] memory balances = new uint256[](_accounts.length); + LibERC1155.ERC1155Storage storage s = LibERC1155.getStorage(); + for (uint256 i = 0; i < _accounts.length; i++) { + balances[i] = s.balanceOf[_ids[i]][_accounts[i]]; + } + return balances; + } + + /// @notice Check if an operator is approved for all tokens of an account + function isApprovedForAll(address _account, address _operator) external view returns (bool) { + return LibERC1155.getStorage().isApprovedForAll[_account][_operator]; + } + + /// @notice Set approval for an operator + function setApprovalForAll(address _operator, bool _approved) external { + LibERC1155.ERC1155Storage storage s = LibERC1155.getStorage(); + s.isApprovedForAll[msg.sender][_operator] = _approved; + } +} diff --git a/test/token/ERC1155/ERC1155/mocks/ERC1155ReceiverMock.sol b/test/token/ERC1155/ERC1155/mocks/ERC1155ReceiverMock.sol new file mode 100644 index 00000000..54c8f2bd --- /dev/null +++ b/test/token/ERC1155/ERC1155/mocks/ERC1155ReceiverMock.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {IERC1155Receiver} from "../../../../../src/interfaces/IERC1155Receiver.sol"; + +/** + * @title ERC1155ReceiverMock + * @notice Mock implementation of IERC1155Receiver for testing transfer acceptance/rejection + * @dev Supports configurable return values and error conditions to test various scenarios + */ +contract ERC1155ReceiverMock is IERC1155Receiver { + enum RevertType { + None, + RevertWithoutMessage, + RevertWithMessage, + RevertWithCustomError, + Panic + } + + bytes4 private immutable _recRetval; + bytes4 private immutable _batRetval; + RevertType private immutable _error; + + event Received(address operator, address from, uint256 id, uint256 value, bytes data, uint256 gas); + event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data, uint256 gas); + + error CustomError(bytes4); + + constructor(bytes4 recRetval, bytes4 batRetval, RevertType error) { + _recRetval = recRetval; + _batRetval = batRetval; + _error = error; + } + + function onERC1155Received(address operator, address from, uint256 id, uint256 value, bytes calldata data) + external + override + returns (bytes4) + { + if (_error == RevertType.RevertWithoutMessage) { + revert(); + } else if (_error == RevertType.RevertWithMessage) { + revert("ERC1155ReceiverMock: reverting on receive"); + } else if (_error == RevertType.RevertWithCustomError) { + revert CustomError(_recRetval); + } else if (_error == RevertType.Panic) { + uint256 a = uint256(0) / uint256(0); + a; + } + + emit Received(operator, from, id, value, data, gasleft()); + return _recRetval; + } + + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external override returns (bytes4) { + if (_error == RevertType.RevertWithoutMessage) { + revert(); + } else if (_error == RevertType.RevertWithMessage) { + revert("ERC1155ReceiverMock: reverting on batch receive"); + } else if (_error == RevertType.RevertWithCustomError) { + revert CustomError(_batRetval); + } else if (_error == RevertType.Panic) { + uint256 a = uint256(0) / uint256(0); + a; + } + + emit BatchReceived(operator, from, ids, values, data, gasleft()); + return _batRetval; + } +} From f5d68faacad592e76c9c23f6ce3b6a7209c22cdd Mon Sep 17 00:00:00 2001 From: aapsi Date: Sun, 2 Nov 2025 12:20:58 +0530 Subject: [PATCH 2/4] test: add comprehensive ERC1155 test suite (112 tests) --- test/token/ERC1155/ERC1155/ERC1155Facet.t.sol | 850 ++++++++++++++++++ test/token/ERC1155/ERC1155/LibERC1155.t.sol | 817 +++++++++++++++++ 2 files changed, 1667 insertions(+) create mode 100644 test/token/ERC1155/ERC1155/ERC1155Facet.t.sol create mode 100644 test/token/ERC1155/ERC1155/LibERC1155.t.sol diff --git a/test/token/ERC1155/ERC1155/ERC1155Facet.t.sol b/test/token/ERC1155/ERC1155/ERC1155Facet.t.sol new file mode 100644 index 00000000..89adae1c --- /dev/null +++ b/test/token/ERC1155/ERC1155/ERC1155Facet.t.sol @@ -0,0 +1,850 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {Test} from "forge-std/Test.sol"; +import {ERC1155FacetHarness} from "./harnesses/ERC1155FacetHarness.sol"; +import {ERC1155Facet} from "../../../../src/token/ERC1155/ERC1155Facet.sol"; +import {ERC1155ReceiverMock} from "./mocks/ERC1155ReceiverMock.sol"; + +contract ERC1155FacetTest is Test { + ERC1155FacetHarness public facet; + + address public alice; + address public bob; + address public charlie; + + string constant DEFAULT_URI = "https://token.uri/{id}.json"; + string constant BASE_URI = "https://base.uri/"; + string constant TOKEN_URI = "token1.json"; + + uint256 constant TOKEN_ID_1 = 1; + uint256 constant TOKEN_ID_2 = 2; + uint256 constant TOKEN_ID_3 = 3; + + bytes4 constant RECEIVER_SINGLE_MAGIC_VALUE = 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) + bytes4 constant RECEIVER_BATCH_MAGIC_VALUE = 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) + + event TransferSingle( + address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value + ); + event TransferBatch( + address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values + ); + event ApprovalForAll(address indexed _account, address indexed _operator, bool _approved); + event URI(string _value, uint256 indexed _id); + + function setUp() public { + alice = makeAddr("alice"); + bob = makeAddr("bob"); + charlie = makeAddr("charlie"); + + facet = new ERC1155FacetHarness(); + facet.initialize(DEFAULT_URI); + } + + // ============================================ + // URI Tests + // ============================================ + + function test_Uri_DefaultUri() public view { + assertEq(facet.uri(TOKEN_ID_1), DEFAULT_URI); + } + + function test_Uri_SetBaseURI() public { + facet.setBaseURI(BASE_URI); + facet.setTokenURI(TOKEN_ID_1, TOKEN_URI); + assertEq(facet.uri(TOKEN_ID_1), string.concat(BASE_URI, TOKEN_URI)); + } + + function test_Uri_SetTokenURI() public { + vm.expectEmit(true, true, true, true); + emit URI(TOKEN_URI, TOKEN_ID_1); + facet.setTokenURI(TOKEN_ID_1, TOKEN_URI); + } + + // ============================================ + // BalanceOf Tests + // ============================================ + + function test_BalanceOf_AfterMint() public { + facet.mint(alice, TOKEN_ID_1, 100); + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 100); + } + + function test_BalanceOf_MultipleTokens() public { + facet.mint(alice, TOKEN_ID_1, 100); + facet.mint(alice, TOKEN_ID_2, 200); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 100); + assertEq(facet.balanceOf(alice, TOKEN_ID_2), 200); + } + + function test_BalanceOf_MultipleAccounts() public { + facet.mint(alice, TOKEN_ID_1, 100); + facet.mint(bob, TOKEN_ID_1, 200); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 100); + assertEq(facet.balanceOf(bob, TOKEN_ID_1), 200); + } + + // ============================================ + // BalanceOfBatch Tests + // ============================================ + + function test_BalanceOfBatch() public { + facet.mint(alice, TOKEN_ID_1, 100); + facet.mint(bob, TOKEN_ID_2, 200); + facet.mint(charlie, TOKEN_ID_3, 300); + + address[] memory accounts = new address[](3); + accounts[0] = alice; + accounts[1] = bob; + accounts[2] = charlie; + + uint256[] memory ids = new uint256[](3); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + ids[2] = TOKEN_ID_3; + + uint256[] memory balances = facet.balanceOfBatch(accounts, ids); + + assertEq(balances[0], 100); + assertEq(balances[1], 200); + assertEq(balances[2], 300); + } + + function test_RevertWhen_BalanceOfBatchArrayLengthMismatch() public { + address[] memory accounts = new address[](2); + accounts[0] = alice; + accounts[1] = bob; + + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidArrayLength.selector, 1, 2)); + facet.balanceOfBatch(accounts, ids); + } + + // ============================================ + // SetApprovalForAll Tests + // ============================================ + + function test_SetApprovalForAll() public { + vm.expectEmit(true, true, true, true); + emit ApprovalForAll(alice, bob, true); + + vm.prank(alice); + facet.setApprovalForAll(bob, true); + + assertTrue(facet.isApprovedForAll(alice, bob)); + } + + function test_SetApprovalForAll_Revoke() public { + vm.prank(alice); + facet.setApprovalForAll(bob, true); + + vm.expectEmit(true, true, true, true); + emit ApprovalForAll(alice, bob, false); + + vm.prank(alice); + facet.setApprovalForAll(bob, false); + + assertFalse(facet.isApprovedForAll(alice, bob)); + } + + function test_SetApprovalForAll_MultipleOperators() public { + vm.startPrank(alice); + facet.setApprovalForAll(bob, true); + facet.setApprovalForAll(charlie, true); + vm.stopPrank(); + + assertTrue(facet.isApprovedForAll(alice, bob)); + assertTrue(facet.isApprovedForAll(alice, charlie)); + } + + function test_RevertWhen_SetApprovalForAllZeroAddress() public { + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidOperator.selector, address(0))); + facet.setApprovalForAll(address(0), true); + } + + function testFuzz_SetApprovalForAll(address owner, address operator, bool approved) public { + vm.assume(owner != address(0) && operator != address(0)); + + vm.prank(owner); + facet.setApprovalForAll(operator, approved); + + assertEq(facet.isApprovedForAll(owner, operator), approved); + } + + // ============================================ + // SafeTransferFrom Tests + // ============================================ + + function test_SafeTransferFrom_ByOwner() public { + facet.mint(alice, TOKEN_ID_1, 100); + + vm.expectEmit(true, true, true, true); + emit TransferSingle(alice, alice, bob, TOKEN_ID_1, 30); + + vm.prank(alice); + facet.safeTransferFrom(alice, bob, TOKEN_ID_1, 30, ""); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(facet.balanceOf(bob, TOKEN_ID_1), 30); + } + + function test_SafeTransferFrom_ByApprovedOperator() public { + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + facet.setApprovalForAll(bob, true); + + vm.expectEmit(true, true, true, true); + emit TransferSingle(bob, alice, charlie, TOKEN_ID_1, 30); + + vm.prank(bob); + facet.safeTransferFrom(alice, charlie, TOKEN_ID_1, 30, ""); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(facet.balanceOf(charlie, TOKEN_ID_1), 30); + } + + function test_SafeTransferFrom_ToSelf() public { + uint256 amount = 100; + + facet.mint(alice, TOKEN_ID_1, amount); + + vm.prank(alice); + facet.safeTransferFrom(alice, alice, TOKEN_ID_1, 50, ""); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), amount); + } + + function test_SafeTransferFrom_ZeroAmount() public { + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + facet.safeTransferFrom(alice, bob, TOKEN_ID_1, 0, ""); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 100); + assertEq(facet.balanceOf(bob, TOKEN_ID_1), 0); + } + + function test_SafeTransferFrom_AllBalance() public { + uint256 amount = 100; + + facet.mint(alice, TOKEN_ID_1, amount); + + vm.prank(alice); + facet.safeTransferFrom(alice, bob, TOKEN_ID_1, amount, ""); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 0); + assertEq(facet.balanceOf(bob, TOKEN_ID_1), amount); + } + + function testFuzz_SafeTransferFrom(address from, address to, uint256 id, uint256 amount) public { + vm.assume(from != address(0) && to != address(0)); + vm.assume(from != to); + vm.assume(from.code.length == 0 && to.code.length == 0); + vm.assume(amount < type(uint256).max / 2); + + facet.mint(from, id, amount); + + vm.prank(from); + facet.safeTransferFrom(from, to, id, amount, ""); + + assertEq(facet.balanceOf(from, id), 0); + assertEq(facet.balanceOf(to, id), amount); + } + + function test_RevertWhen_SafeTransferFromToZeroAddress() public { + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(0))); + facet.safeTransferFrom(alice, address(0), TOKEN_ID_1, 30, ""); + } + + function test_RevertWhen_SafeTransferFromFromZeroAddress() public { + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidSender.selector, address(0))); + facet.safeTransferFrom(address(0), bob, TOKEN_ID_1, 30, ""); + } + + function test_RevertWhen_SafeTransferFromWithoutApproval() public { + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(bob); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155MissingApprovalForAll.selector, bob, alice)); + facet.safeTransferFrom(alice, charlie, TOKEN_ID_1, 30, ""); + } + + function test_RevertWhen_SafeTransferFromInsufficientBalance() public { + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert( + abi.encodeWithSelector(ERC1155Facet.ERC1155InsufficientBalance.selector, alice, 100, 150, TOKEN_ID_1) + ); + facet.safeTransferFrom(alice, bob, TOKEN_ID_1, 150, ""); + } + + function test_RevertWhen_SafeTransferFromZeroBalance() public { + vm.prank(alice); + vm.expectRevert( + abi.encodeWithSelector(ERC1155Facet.ERC1155InsufficientBalance.selector, alice, 0, 1, TOKEN_ID_1) + ); + facet.safeTransferFrom(alice, bob, TOKEN_ID_1, 1, ""); + } + + function test_RevertWhen_MintOverflowsRecipient() public { + uint256 nearMaxBalance = type(uint256).max - 100; + + // Mint near-max tokens to alice + facet.mint(alice, TOKEN_ID_1, nearMaxBalance); + + // Try to mint more, which would overflow + vm.expectRevert(); // Arithmetic overflow + facet.mint(alice, TOKEN_ID_1, 200); + } + + // ============================================ + // SafeBatchTransferFrom Tests + // ============================================ + + function test_SafeBatchTransferFrom_ByOwner() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 100; + amounts[1] = 200; + + facet.mintBatch(alice, ids, amounts); + + uint256[] memory transferAmounts = new uint256[](2); + transferAmounts[0] = 30; + transferAmounts[1] = 50; + + vm.expectEmit(true, true, true, true); + emit TransferBatch(alice, alice, bob, ids, transferAmounts); + + vm.prank(alice); + facet.safeBatchTransferFrom(alice, bob, ids, transferAmounts, ""); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(facet.balanceOf(alice, TOKEN_ID_2), 150); + assertEq(facet.balanceOf(bob, TOKEN_ID_1), 30); + assertEq(facet.balanceOf(bob, TOKEN_ID_2), 50); + } + + function test_SafeBatchTransferFrom_ByApprovedOperator() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 100; + amounts[1] = 200; + + facet.mintBatch(alice, ids, amounts); + + vm.prank(alice); + facet.setApprovalForAll(bob, true); + + uint256[] memory transferAmounts = new uint256[](2); + transferAmounts[0] = 30; + transferAmounts[1] = 50; + + vm.prank(bob); + facet.safeBatchTransferFrom(alice, charlie, ids, transferAmounts, ""); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(facet.balanceOf(alice, TOKEN_ID_2), 150); + assertEq(facet.balanceOf(charlie, TOKEN_ID_1), 30); + assertEq(facet.balanceOf(charlie, TOKEN_ID_2), 50); + } + + function test_RevertWhen_SafeBatchTransferFromToZeroAddress() public { + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 30; + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(0))); + facet.safeBatchTransferFrom(alice, address(0), ids, amounts, ""); + } + + function test_RevertWhen_SafeBatchTransferFromFromZeroAddress() public { + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 30; + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidSender.selector, address(0))); + facet.safeBatchTransferFrom(address(0), bob, ids, amounts, ""); + } + + function test_RevertWhen_SafeBatchTransferFromArrayLengthMismatch() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 30; + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidArrayLength.selector, 2, 1)); + facet.safeBatchTransferFrom(alice, bob, ids, amounts, ""); + } + + function test_RevertWhen_SafeBatchTransferFromWithoutApproval() public { + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 30; + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(bob); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155MissingApprovalForAll.selector, bob, alice)); + facet.safeBatchTransferFrom(alice, charlie, ids, amounts, ""); + } + + function test_RevertWhen_SafeBatchTransferFromInsufficientBalance() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory mintAmounts = new uint256[](2); + mintAmounts[0] = 100; + mintAmounts[1] = 50; + + facet.mintBatch(alice, ids, mintAmounts); + + uint256[] memory transferAmounts = new uint256[](2); + transferAmounts[0] = 30; + transferAmounts[1] = 100; // More than balance + + vm.prank(alice); + vm.expectRevert( + abi.encodeWithSelector(ERC1155Facet.ERC1155InsufficientBalance.selector, alice, 50, 100, TOKEN_ID_2) + ); + facet.safeBatchTransferFrom(alice, bob, ids, transferAmounts, ""); + } + + // ============================================ + // Mint Tests (via Harness) + // ============================================ + + function test_Mint() public { + uint256 amount = 100; + + vm.expectEmit(true, true, true, true); + emit TransferSingle(address(this), address(0), alice, TOKEN_ID_1, amount); + facet.mint(alice, TOKEN_ID_1, amount); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), amount); + } + + function test_Mint_Multiple() public { + facet.mint(alice, TOKEN_ID_1, 100); + facet.mint(bob, TOKEN_ID_1, 200); + facet.mint(alice, TOKEN_ID_2, 50); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 100); + assertEq(facet.balanceOf(bob, TOKEN_ID_1), 200); + assertEq(facet.balanceOf(alice, TOKEN_ID_2), 50); + } + + function test_RevertWhen_MintToZeroAddress() public { + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(0))); + facet.mint(address(0), TOKEN_ID_1, 100); + } + + // ============================================ + // MintBatch Tests (via Harness) + // ============================================ + + function test_MintBatch() public { + uint256[] memory ids = new uint256[](3); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + ids[2] = TOKEN_ID_3; + + uint256[] memory amounts = new uint256[](3); + amounts[0] = 100; + amounts[1] = 200; + amounts[2] = 300; + + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), alice, ids, amounts); + facet.mintBatch(alice, ids, amounts); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 100); + assertEq(facet.balanceOf(alice, TOKEN_ID_2), 200); + assertEq(facet.balanceOf(alice, TOKEN_ID_3), 300); + } + + function test_RevertWhen_MintBatchToZeroAddress() public { + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 100; + + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(0))); + facet.mintBatch(address(0), ids, amounts); + } + + function test_RevertWhen_MintBatchArrayLengthMismatch() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 100; + + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidArrayLength.selector, 2, 1)); + facet.mintBatch(alice, ids, amounts); + } + + // ============================================ + // Burn Tests (via Harness) + // ============================================ + + function test_Burn() public { + facet.mint(alice, TOKEN_ID_1, 100); + + vm.expectEmit(true, true, true, true); + emit TransferSingle(address(this), alice, address(0), TOKEN_ID_1, 30); + facet.burn(alice, TOKEN_ID_1, 30); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); + } + + function test_Burn_AllBalance() public { + facet.mint(alice, TOKEN_ID_1, 100); + + facet.burn(alice, TOKEN_ID_1, 100); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 0); + } + + function test_RevertWhen_BurnFromZeroAddress() public { + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidSender.selector, address(0))); + facet.burn(address(0), TOKEN_ID_1, 100); + } + + function test_RevertWhen_BurnInsufficientBalance() public { + facet.mint(alice, TOKEN_ID_1, 100); + + vm.expectRevert( + abi.encodeWithSelector(ERC1155Facet.ERC1155InsufficientBalance.selector, alice, 100, 150, TOKEN_ID_1) + ); + facet.burn(alice, TOKEN_ID_1, 150); + } + + function test_RevertWhen_BurnZeroBalance() public { + vm.expectRevert( + abi.encodeWithSelector(ERC1155Facet.ERC1155InsufficientBalance.selector, alice, 0, 1, TOKEN_ID_1) + ); + facet.burn(alice, TOKEN_ID_1, 1); + } + + // ============================================ + // BurnBatch Tests (via Harness) + // ============================================ + + function test_BurnBatch() public { + uint256[] memory ids = new uint256[](3); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + ids[2] = TOKEN_ID_3; + + uint256[] memory mintAmounts = new uint256[](3); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + + facet.mintBatch(alice, ids, mintAmounts); + + uint256[] memory burnAmounts = new uint256[](3); + burnAmounts[0] = 30; + burnAmounts[1] = 50; + burnAmounts[2] = 100; + + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), alice, address(0), ids, burnAmounts); + facet.burnBatch(alice, ids, burnAmounts); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(facet.balanceOf(alice, TOKEN_ID_2), 150); + assertEq(facet.balanceOf(alice, TOKEN_ID_3), 200); + } + + function test_RevertWhen_BurnBatchFromZeroAddress() public { + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 100; + + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidSender.selector, address(0))); + facet.burnBatch(address(0), ids, amounts); + } + + function test_RevertWhen_BurnBatchArrayLengthMismatch() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 100; + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidArrayLength.selector, 2, 1)); + facet.burnBatch(alice, ids, amounts); + } + + function test_RevertWhen_BurnBatchInsufficientBalance() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory mintAmounts = new uint256[](2); + mintAmounts[0] = 100; + mintAmounts[1] = 50; + + facet.mintBatch(alice, ids, mintAmounts); + + uint256[] memory burnAmounts = new uint256[](2); + burnAmounts[0] = 50; + burnAmounts[1] = 100; // More than balance + + vm.expectRevert( + abi.encodeWithSelector(ERC1155Facet.ERC1155InsufficientBalance.selector, alice, 50, 100, TOKEN_ID_2) + ); + facet.burnBatch(alice, ids, burnAmounts); + } + + // ============================================ + // Receiver Hook Tests + // ============================================ + + function test_SafeTransferFrom_ToContractWithAcceptance() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None + ); + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, ""); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 50); + assertEq(facet.balanceOf(address(receiver), TOKEN_ID_1), 50); + } + + function test_RevertWhen_SafeTransferFrom_ToContractWithWrongReturnValue() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + 0x00c0ffee, // Wrong return value + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.None + ); + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(receiver))); + facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, ""); + } + + function test_RevertWhen_SafeTransferFrom_ToContractWithRevertMessage() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithMessage + ); + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert("ERC1155ReceiverMock: reverting on receive"); + facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, ""); + } + + function test_RevertWhen_SafeTransferFrom_ToContractWithRevertNoMessage() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithoutMessage + ); + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(receiver))); + facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, ""); + } + + function test_SafeBatchTransferFrom_ToContractWithAcceptance() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None + ); + + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 100; + amounts[1] = 200; + + facet.mintBatch(alice, ids, amounts); + + uint256[] memory transferAmounts = new uint256[](2); + transferAmounts[0] = 50; + transferAmounts[1] = 100; + + vm.prank(alice); + facet.safeBatchTransferFrom(alice, address(receiver), ids, transferAmounts, ""); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 50); + assertEq(facet.balanceOf(alice, TOKEN_ID_2), 100); + assertEq(facet.balanceOf(address(receiver), TOKEN_ID_1), 50); + assertEq(facet.balanceOf(address(receiver), TOKEN_ID_2), 100); + } + + function test_RevertWhen_SafeBatchTransferFrom_ToContractWithWrongReturnValue() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_SINGLE_MAGIC_VALUE, // Wrong return value (correct for single, wrong for batch) + ERC1155ReceiverMock.RevertType.None + ); + + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 50; + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(receiver))); + facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, ""); + } + + function test_RevertWhen_SafeBatchTransferFrom_ToContractWithRevertMessage() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithMessage + ); + + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 50; + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert("ERC1155ReceiverMock: reverting on batch receive"); + facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, ""); + } + + // ============================================ + // Integration Tests + // ============================================ + + function test_MintTransferBurn_Flow() public { + // Mint to alice + facet.mint(alice, TOKEN_ID_1, 1000); + facet.mint(alice, TOKEN_ID_2, 500); + + // Alice transfers some to bob + vm.prank(alice); + facet.safeTransferFrom(alice, bob, TOKEN_ID_1, 300, ""); + + // Bob transfers some to charlie + vm.prank(bob); + facet.safeTransferFrom(bob, charlie, TOKEN_ID_1, 100, ""); + + // Burn from alice + facet.burn(alice, TOKEN_ID_1, 200); + + // Verify final balances + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 500); + assertEq(facet.balanceOf(alice, TOKEN_ID_2), 500); + assertEq(facet.balanceOf(bob, TOKEN_ID_1), 200); + assertEq(facet.balanceOf(charlie, TOKEN_ID_1), 100); + } + + function test_MintBatchTransferBatchBurnBatch_Flow() public { + uint256[] memory ids = new uint256[](3); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + ids[2] = TOKEN_ID_3; + + uint256[] memory mintAmounts = new uint256[](3); + mintAmounts[0] = 1000; + mintAmounts[1] = 2000; + mintAmounts[2] = 3000; + + // Mint batch to alice + facet.mintBatch(alice, ids, mintAmounts); + + // Alice transfers batch to bob + uint256[] memory transferAmounts = new uint256[](3); + transferAmounts[0] = 300; + transferAmounts[1] = 400; + transferAmounts[2] = 500; + + vm.prank(alice); + facet.safeBatchTransferFrom(alice, bob, ids, transferAmounts, ""); + + // Burn batch from alice + uint256[] memory burnAmounts = new uint256[](3); + burnAmounts[0] = 200; + burnAmounts[1] = 300; + burnAmounts[2] = 400; + + facet.burnBatch(alice, ids, burnAmounts); + + // Verify final balances + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 500); + assertEq(facet.balanceOf(alice, TOKEN_ID_2), 1300); + assertEq(facet.balanceOf(alice, TOKEN_ID_3), 2100); + assertEq(facet.balanceOf(bob, TOKEN_ID_1), 300); + assertEq(facet.balanceOf(bob, TOKEN_ID_2), 400); + assertEq(facet.balanceOf(bob, TOKEN_ID_3), 500); + } + + function test_ApprovalAndTransfer_Flow() public { + facet.mint(alice, TOKEN_ID_1, 1000); + + // Alice approves bob + vm.prank(alice); + facet.setApprovalForAll(bob, true); + + // Bob transfers on behalf of alice + vm.prank(bob); + facet.safeTransferFrom(alice, charlie, TOKEN_ID_1, 300, ""); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 700); + assertEq(facet.balanceOf(charlie, TOKEN_ID_1), 300); + + // Alice revokes approval + vm.prank(alice); + facet.setApprovalForAll(bob, false); + + // Bob can no longer transfer + vm.prank(bob); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155MissingApprovalForAll.selector, bob, alice)); + facet.safeTransferFrom(alice, charlie, TOKEN_ID_1, 100, ""); + } +} diff --git a/test/token/ERC1155/ERC1155/LibERC1155.t.sol b/test/token/ERC1155/ERC1155/LibERC1155.t.sol new file mode 100644 index 00000000..b7979dd6 --- /dev/null +++ b/test/token/ERC1155/ERC1155/LibERC1155.t.sol @@ -0,0 +1,817 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {Test} from "forge-std/Test.sol"; +import {LibERC1155Harness} from "./harnesses/LibERC1155Harness.sol"; +import {LibERC1155} from "../../../../src/token/ERC1155/LibERC1155.sol"; +import {ERC1155ReceiverMock} from "./mocks/ERC1155ReceiverMock.sol"; + +contract LibERC1155Test is Test { + LibERC1155Harness public harness; + + address public alice; + address public bob; + address public charlie; + + string constant DEFAULT_URI = "https://token.uri/{id}.json"; + string constant BASE_URI = "https://base.uri/"; + string constant TOKEN_URI = "token1.json"; + + uint256 constant TOKEN_ID_1 = 1; + uint256 constant TOKEN_ID_2 = 2; + uint256 constant TOKEN_ID_3 = 3; + + bytes4 constant RECEIVER_SINGLE_MAGIC_VALUE = 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) + bytes4 constant RECEIVER_BATCH_MAGIC_VALUE = 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) + + event TransferSingle( + address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value + ); + event TransferBatch( + address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values + ); + event ApprovalForAll(address indexed _account, address indexed _operator, bool _approved); + event URI(string _value, uint256 indexed _id); + + function setUp() public { + alice = makeAddr("alice"); + bob = makeAddr("bob"); + charlie = makeAddr("charlie"); + + harness = new LibERC1155Harness(); + harness.initialize(DEFAULT_URI); + } + + // ============================================ + // URI Tests + // ============================================ + + function test_Uri_DefaultUri() public view { + assertEq(harness.uri(TOKEN_ID_1), DEFAULT_URI); + } + + function test_Uri_SetBaseURI() public { + harness.setBaseURI(BASE_URI); + harness.setTokenURI(TOKEN_ID_1, TOKEN_URI); + assertEq(harness.uri(TOKEN_ID_1), string.concat(BASE_URI, TOKEN_URI)); + } + + function test_Uri_SetTokenURI() public { + vm.expectEmit(true, true, true, true); + emit URI(TOKEN_URI, TOKEN_ID_1); + harness.setTokenURI(TOKEN_ID_1, TOKEN_URI); + } + + // ============================================ + // Mint Tests + // ============================================ + + function test_Mint() public { + uint256 amount = 100; + + vm.expectEmit(true, true, true, true); + emit TransferSingle(address(this), address(0), alice, TOKEN_ID_1, amount); + harness.mint(alice, TOKEN_ID_1, amount); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), amount); + } + + function test_Mint_Multiple() public { + harness.mint(alice, TOKEN_ID_1, 100); + harness.mint(bob, TOKEN_ID_1, 200); + harness.mint(alice, TOKEN_ID_2, 50); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 100); + assertEq(harness.balanceOf(bob, TOKEN_ID_1), 200); + assertEq(harness.balanceOf(alice, TOKEN_ID_2), 50); + } + + function test_Mint_SameTokenMultipleTimes() public { + harness.mint(alice, TOKEN_ID_1, 100); + harness.mint(alice, TOKEN_ID_1, 50); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 150); + } + + function testFuzz_Mint(address to, uint256 id, uint256 amount) public { + vm.assume(to != address(0)); + vm.assume(to.code.length == 0); + vm.assume(amount < type(uint256).max / 2); + + harness.mint(to, id, amount); + + assertEq(harness.balanceOf(to, id), amount); + } + + function test_RevertWhen_MintToZeroAddress() public { + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidReceiver.selector, address(0))); + harness.mint(address(0), TOKEN_ID_1, 100); + } + + // ============================================ + // MintBatch Tests + // ============================================ + + function test_MintBatch() public { + uint256[] memory ids = new uint256[](3); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + ids[2] = TOKEN_ID_3; + + uint256[] memory amounts = new uint256[](3); + amounts[0] = 100; + amounts[1] = 200; + amounts[2] = 300; + + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), address(0), alice, ids, amounts); + harness.mintBatch(alice, ids, amounts); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 100); + assertEq(harness.balanceOf(alice, TOKEN_ID_2), 200); + assertEq(harness.balanceOf(alice, TOKEN_ID_3), 300); + } + + function test_RevertWhen_MintBatchToZeroAddress() public { + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 100; + + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidReceiver.selector, address(0))); + harness.mintBatch(address(0), ids, amounts); + } + + function test_RevertWhen_MintBatchArrayLengthMismatch() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 100; + + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidArrayLength.selector, 2, 1)); + harness.mintBatch(alice, ids, amounts); + } + + // ============================================ + // Burn Tests + // ============================================ + + function test_Burn() public { + harness.mint(alice, TOKEN_ID_1, 100); + + vm.expectEmit(true, true, true, true); + emit TransferSingle(address(this), alice, address(0), TOKEN_ID_1, 30); + harness.burn(alice, TOKEN_ID_1, 30); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); + } + + function test_Burn_AllBalance() public { + harness.mint(alice, TOKEN_ID_1, 100); + + harness.burn(alice, TOKEN_ID_1, 100); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 0); + } + + function testFuzz_Burn(address from, uint256 id, uint256 mintAmount, uint256 burnAmount) public { + vm.assume(from != address(0)); + vm.assume(from.code.length == 0); + vm.assume(mintAmount < type(uint256).max / 2); + vm.assume(burnAmount <= mintAmount); + + harness.mint(from, id, mintAmount); + harness.burn(from, id, burnAmount); + + assertEq(harness.balanceOf(from, id), mintAmount - burnAmount); + } + + function test_RevertWhen_BurnFromZeroAddress() public { + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidSender.selector, address(0))); + harness.burn(address(0), TOKEN_ID_1, 100); + } + + function test_RevertWhen_BurnInsufficientBalance() public { + harness.mint(alice, TOKEN_ID_1, 100); + + vm.expectRevert( + abi.encodeWithSelector(LibERC1155.ERC1155InsufficientBalance.selector, alice, 100, 150, TOKEN_ID_1) + ); + harness.burn(alice, TOKEN_ID_1, 150); + } + + function test_RevertWhen_BurnZeroBalance() public { + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InsufficientBalance.selector, alice, 0, 1, TOKEN_ID_1)); + harness.burn(alice, TOKEN_ID_1, 1); + } + + // ============================================ + // BurnBatch Tests + // ============================================ + + function test_BurnBatch() public { + uint256[] memory ids = new uint256[](3); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + ids[2] = TOKEN_ID_3; + + uint256[] memory mintAmounts = new uint256[](3); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + + harness.mintBatch(alice, ids, mintAmounts); + + uint256[] memory burnAmounts = new uint256[](3); + burnAmounts[0] = 30; + burnAmounts[1] = 50; + burnAmounts[2] = 100; + + vm.expectEmit(true, true, true, true); + emit TransferBatch(address(this), alice, address(0), ids, burnAmounts); + harness.burnBatch(alice, ids, burnAmounts); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(harness.balanceOf(alice, TOKEN_ID_2), 150); + assertEq(harness.balanceOf(alice, TOKEN_ID_3), 200); + } + + function test_RevertWhen_BurnBatchFromZeroAddress() public { + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 100; + + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidSender.selector, address(0))); + harness.burnBatch(address(0), ids, amounts); + } + + function test_RevertWhen_BurnBatchArrayLengthMismatch() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 100; + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidArrayLength.selector, 2, 1)); + harness.burnBatch(alice, ids, amounts); + } + + function test_RevertWhen_BurnBatchInsufficientBalance() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory mintAmounts = new uint256[](2); + mintAmounts[0] = 100; + mintAmounts[1] = 50; + + harness.mintBatch(alice, ids, mintAmounts); + + uint256[] memory burnAmounts = new uint256[](2); + burnAmounts[0] = 50; + burnAmounts[1] = 100; // More than balance + + vm.expectRevert( + abi.encodeWithSelector(LibERC1155.ERC1155InsufficientBalance.selector, alice, 50, 100, TOKEN_ID_2) + ); + harness.burnBatch(alice, ids, burnAmounts); + } + + // ============================================ + // SafeTransferFrom Tests + // ============================================ + + function test_SafeTransferFrom_ByOwner() public { + harness.mint(alice, TOKEN_ID_1, 100); + + vm.expectEmit(true, true, true, true); + emit TransferSingle(alice, alice, bob, TOKEN_ID_1, 30); + + vm.prank(alice); + harness.safeTransferFrom(alice, bob, TOKEN_ID_1, 30); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(harness.balanceOf(bob, TOKEN_ID_1), 30); + } + + function test_SafeTransferFrom_ByApprovedOperator() public { + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + harness.setApprovalForAll(bob, true); + + vm.prank(bob); + harness.safeTransferFrom(alice, charlie, TOKEN_ID_1, 30); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(harness.balanceOf(charlie, TOKEN_ID_1), 30); + } + + function test_SafeTransferFrom_ToSelf() public { + uint256 amount = 100; + + harness.mint(alice, TOKEN_ID_1, amount); + + vm.prank(alice); + harness.safeTransferFrom(alice, alice, TOKEN_ID_1, 50); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), amount); + } + + function test_SafeTransferFrom_ZeroAmount() public { + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + harness.safeTransferFrom(alice, bob, TOKEN_ID_1, 0); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 100); + assertEq(harness.balanceOf(bob, TOKEN_ID_1), 0); + } + + function testFuzz_SafeTransferFrom(address from, address to, uint256 id, uint256 amount) public { + vm.assume(from != address(0) && to != address(0)); + vm.assume(from.code.length == 0 && to.code.length == 0); + vm.assume(amount < type(uint256).max / 2); + + harness.mint(from, id, amount); + + vm.prank(from); + harness.safeTransferFrom(from, to, id, amount); + + assertEq(harness.balanceOf(from, id), 0); + assertEq(harness.balanceOf(to, id), amount); + } + + function test_RevertWhen_SafeTransferFromToZeroAddress() public { + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidReceiver.selector, address(0))); + harness.safeTransferFrom(alice, address(0), TOKEN_ID_1, 30); + } + + function test_RevertWhen_SafeTransferFromFromZeroAddress() public { + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidSender.selector, address(0))); + harness.safeTransferFrom(address(0), bob, TOKEN_ID_1, 30); + } + + function test_RevertWhen_SafeTransferFromWithoutApproval() public { + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(bob); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155MissingApprovalForAll.selector, bob, alice)); + harness.safeTransferFrom(alice, charlie, TOKEN_ID_1, 30); + } + + function test_RevertWhen_SafeTransferFromInsufficientBalance() public { + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert( + abi.encodeWithSelector(LibERC1155.ERC1155InsufficientBalance.selector, alice, 100, 150, TOKEN_ID_1) + ); + harness.safeTransferFrom(alice, bob, TOKEN_ID_1, 150); + } + + function test_RevertWhen_SafeTransferFromZeroBalance() public { + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InsufficientBalance.selector, alice, 0, 1, TOKEN_ID_1)); + harness.safeTransferFrom(alice, bob, TOKEN_ID_1, 1); + } + + function test_RevertWhen_MintOverflowsRecipient() public { + uint256 nearMaxBalance = type(uint256).max - 100; + + // Mint near-max tokens to alice + harness.mint(alice, TOKEN_ID_1, nearMaxBalance); + + // Try to mint more, which would overflow + vm.expectRevert(); // Arithmetic overflow + harness.mint(alice, TOKEN_ID_1, 200); + } + + // ============================================ + // SafeBatchTransferFrom Tests + // ============================================ + + function test_SafeBatchTransferFrom_ByOwner() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 100; + amounts[1] = 200; + + harness.mintBatch(alice, ids, amounts); + + uint256[] memory transferAmounts = new uint256[](2); + transferAmounts[0] = 30; + transferAmounts[1] = 50; + + vm.expectEmit(true, true, true, true); + emit TransferBatch(alice, alice, bob, ids, transferAmounts); + + vm.prank(alice); + harness.safeBatchTransferFrom(alice, bob, ids, transferAmounts); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(harness.balanceOf(alice, TOKEN_ID_2), 150); + assertEq(harness.balanceOf(bob, TOKEN_ID_1), 30); + assertEq(harness.balanceOf(bob, TOKEN_ID_2), 50); + } + + function test_SafeBatchTransferFrom_ByApprovedOperator() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 100; + amounts[1] = 200; + + harness.mintBatch(alice, ids, amounts); + + vm.prank(alice); + harness.setApprovalForAll(bob, true); + + uint256[] memory transferAmounts = new uint256[](2); + transferAmounts[0] = 30; + transferAmounts[1] = 50; + + vm.prank(bob); + harness.safeBatchTransferFrom(alice, charlie, ids, transferAmounts); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(harness.balanceOf(alice, TOKEN_ID_2), 150); + assertEq(harness.balanceOf(charlie, TOKEN_ID_1), 30); + assertEq(harness.balanceOf(charlie, TOKEN_ID_2), 50); + } + + function test_RevertWhen_SafeBatchTransferFromToZeroAddress() public { + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 30; + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidReceiver.selector, address(0))); + harness.safeBatchTransferFrom(alice, address(0), ids, amounts); + } + + function test_RevertWhen_SafeBatchTransferFromFromZeroAddress() public { + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 30; + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidSender.selector, address(0))); + harness.safeBatchTransferFrom(address(0), bob, ids, amounts); + } + + function test_RevertWhen_SafeBatchTransferFromArrayLengthMismatch() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 30; + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidArrayLength.selector, 2, 1)); + harness.safeBatchTransferFrom(alice, bob, ids, amounts); + } + + function test_RevertWhen_SafeBatchTransferFromWithoutApproval() public { + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 30; + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(bob); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155MissingApprovalForAll.selector, bob, alice)); + harness.safeBatchTransferFrom(alice, charlie, ids, amounts); + } + + function test_RevertWhen_SafeBatchTransferFromInsufficientBalance() public { + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory mintAmounts = new uint256[](2); + mintAmounts[0] = 100; + mintAmounts[1] = 50; + + harness.mintBatch(alice, ids, mintAmounts); + + uint256[] memory transferAmounts = new uint256[](2); + transferAmounts[0] = 30; + transferAmounts[1] = 100; // More than balance + + vm.prank(alice); + vm.expectRevert( + abi.encodeWithSelector(LibERC1155.ERC1155InsufficientBalance.selector, alice, 50, 100, TOKEN_ID_2) + ); + harness.safeBatchTransferFrom(alice, bob, ids, transferAmounts); + } + + // ============================================ + // BalanceOf Tests + // ============================================ + + function test_BalanceOf_AfterMint() public { + harness.mint(alice, TOKEN_ID_1, 100); + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 100); + } + + function test_BalanceOf_MultipleTokens() public { + harness.mint(alice, TOKEN_ID_1, 100); + harness.mint(alice, TOKEN_ID_2, 200); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 100); + assertEq(harness.balanceOf(alice, TOKEN_ID_2), 200); + } + + // ============================================ + // BalanceOfBatch Tests + // ============================================ + + function test_BalanceOfBatch() public { + harness.mint(alice, TOKEN_ID_1, 100); + harness.mint(bob, TOKEN_ID_2, 200); + harness.mint(charlie, TOKEN_ID_3, 300); + + address[] memory accounts = new address[](3); + accounts[0] = alice; + accounts[1] = bob; + accounts[2] = charlie; + + uint256[] memory ids = new uint256[](3); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + ids[2] = TOKEN_ID_3; + + uint256[] memory balances = harness.balanceOfBatch(accounts, ids); + + assertEq(balances[0], 100); + assertEq(balances[1], 200); + assertEq(balances[2], 300); + } + + // ============================================ + // Approval Tests + // ============================================ + + function test_SetApprovalForAll() public { + vm.prank(alice); + harness.setApprovalForAll(bob, true); + + assertTrue(harness.isApprovedForAll(alice, bob)); + } + + function test_SetApprovalForAll_Revoke() public { + vm.prank(alice); + harness.setApprovalForAll(bob, true); + + vm.prank(alice); + harness.setApprovalForAll(bob, false); + + assertFalse(harness.isApprovedForAll(alice, bob)); + } + + function testFuzz_SetApprovalForAll(address owner, address operator, bool approved) public { + vm.assume(owner != address(0) && operator != address(0)); + + vm.prank(owner); + harness.setApprovalForAll(operator, approved); + + assertEq(harness.isApprovedForAll(owner, operator), approved); + } + + // ============================================ + // Receiver Hook Tests + // ============================================ + + function test_SafeTransferFrom_ToContractWithAcceptance() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None + ); + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + harness.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 50); + assertEq(harness.balanceOf(address(receiver), TOKEN_ID_1), 50); + } + + function test_RevertWhen_SafeTransferFrom_ToContractWithWrongReturnValue() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + 0x00c0ffee, // Wrong return value + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.None + ); + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidReceiver.selector, address(receiver))); + harness.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50); + } + + function test_RevertWhen_SafeTransferFrom_ToContractWithRevertMessage() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithMessage + ); + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert("ERC1155ReceiverMock: reverting on receive"); + harness.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50); + } + + function test_RevertWhen_SafeTransferFrom_ToContractWithRevertNoMessage() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithoutMessage + ); + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidReceiver.selector, address(receiver))); + harness.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50); + } + + function test_SafeBatchTransferFrom_ToContractWithAcceptance() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None + ); + + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 100; + amounts[1] = 200; + + harness.mintBatch(alice, ids, amounts); + + uint256[] memory transferAmounts = new uint256[](2); + transferAmounts[0] = 50; + transferAmounts[1] = 100; + + vm.prank(alice); + harness.safeBatchTransferFrom(alice, address(receiver), ids, transferAmounts); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 50); + assertEq(harness.balanceOf(alice, TOKEN_ID_2), 100); + assertEq(harness.balanceOf(address(receiver), TOKEN_ID_1), 50); + assertEq(harness.balanceOf(address(receiver), TOKEN_ID_2), 100); + } + + function test_RevertWhen_SafeBatchTransferFrom_ToContractWithWrongReturnValue() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_SINGLE_MAGIC_VALUE, // Wrong return value (correct for single, wrong for batch) + ERC1155ReceiverMock.RevertType.None + ); + + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 50; + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidReceiver.selector, address(receiver))); + harness.safeBatchTransferFrom(alice, address(receiver), ids, amounts); + } + + function test_RevertWhen_SafeBatchTransferFrom_ToContractWithRevertMessage() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithMessage + ); + + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 50; + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert("ERC1155ReceiverMock: reverting on batch receive"); + harness.safeBatchTransferFrom(alice, address(receiver), ids, amounts); + } + + // ============================================ + // Integration Tests + // ============================================ + + function test_MintTransferBurn_Flow() public { + // Mint to alice + harness.mint(alice, TOKEN_ID_1, 1000); + harness.mint(alice, TOKEN_ID_2, 500); + + // Alice transfers some to bob + vm.prank(alice); + harness.safeTransferFrom(alice, bob, TOKEN_ID_1, 300); + + // Bob transfers some to charlie + vm.prank(bob); + harness.safeTransferFrom(bob, charlie, TOKEN_ID_1, 100); + + // Burn from alice + harness.burn(alice, TOKEN_ID_1, 200); + + // Verify final balances + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 500); + assertEq(harness.balanceOf(alice, TOKEN_ID_2), 500); + assertEq(harness.balanceOf(bob, TOKEN_ID_1), 200); + assertEq(harness.balanceOf(charlie, TOKEN_ID_1), 100); + } + + function test_MintBatchTransferBatchBurnBatch_Flow() public { + uint256[] memory ids = new uint256[](3); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + ids[2] = TOKEN_ID_3; + + uint256[] memory mintAmounts = new uint256[](3); + mintAmounts[0] = 1000; + mintAmounts[1] = 2000; + mintAmounts[2] = 3000; + + // Mint batch to alice + harness.mintBatch(alice, ids, mintAmounts); + + // Alice transfers batch to bob + uint256[] memory transferAmounts = new uint256[](3); + transferAmounts[0] = 300; + transferAmounts[1] = 400; + transferAmounts[2] = 500; + + vm.prank(alice); + harness.safeBatchTransferFrom(alice, bob, ids, transferAmounts); + + // Burn batch from alice + uint256[] memory burnAmounts = new uint256[](3); + burnAmounts[0] = 200; + burnAmounts[1] = 300; + burnAmounts[2] = 400; + + harness.burnBatch(alice, ids, burnAmounts); + + // Verify final balances + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 500); + assertEq(harness.balanceOf(alice, TOKEN_ID_2), 1300); + assertEq(harness.balanceOf(alice, TOKEN_ID_3), 2100); + assertEq(harness.balanceOf(bob, TOKEN_ID_1), 300); + assertEq(harness.balanceOf(bob, TOKEN_ID_2), 400); + assertEq(harness.balanceOf(bob, TOKEN_ID_3), 500); + } + + function test_ApprovalAndTransfer_Flow() public { + harness.mint(alice, TOKEN_ID_1, 1000); + + // Alice approves bob + vm.prank(alice); + harness.setApprovalForAll(bob, true); + + // Bob transfers on behalf of alice + vm.prank(bob); + harness.safeTransferFrom(alice, charlie, TOKEN_ID_1, 300); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 700); + assertEq(harness.balanceOf(charlie, TOKEN_ID_1), 300); + + // Alice revokes approval + vm.prank(alice); + harness.setApprovalForAll(bob, false); + + // Bob can no longer transfer + vm.prank(bob); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155MissingApprovalForAll.selector, bob, alice)); + harness.safeTransferFrom(alice, charlie, TOKEN_ID_1, 100); + } +} From a31af6303e45c7bb43ed20ea3911dec65f606c50 Mon Sep 17 00:00:00 2001 From: aapsi Date: Sun, 2 Nov 2025 23:17:46 +0530 Subject: [PATCH 3/4] test: enhance ERC1155 test suite with additional scenarios for balance checks, minting, and safe transfers --- test/token/ERC1155/ERC1155/ERC1155Facet.t.sol | 268 ++++++++++++++++- test/token/ERC1155/ERC1155/LibERC1155.t.sol | 274 ++++++++++++++++++ 2 files changed, 526 insertions(+), 16 deletions(-) diff --git a/test/token/ERC1155/ERC1155/ERC1155Facet.t.sol b/test/token/ERC1155/ERC1155/ERC1155Facet.t.sol index 89adae1c..17c4e4e2 100644 --- a/test/token/ERC1155/ERC1155/ERC1155Facet.t.sol +++ b/test/token/ERC1155/ERC1155/ERC1155Facet.t.sol @@ -79,12 +79,8 @@ contract ERC1155FacetTest is Test { assertEq(facet.balanceOf(alice, TOKEN_ID_2), 200); } - function test_BalanceOf_MultipleAccounts() public { - facet.mint(alice, TOKEN_ID_1, 100); - facet.mint(bob, TOKEN_ID_1, 200); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 100); - assertEq(facet.balanceOf(bob, TOKEN_ID_1), 200); + function test_BalanceOf_ZeroAddress() public view { + assertEq(facet.balanceOf(address(0), TOKEN_ID_1), 0); } // ============================================ @@ -125,6 +121,68 @@ contract ERC1155FacetTest is Test { facet.balanceOfBatch(accounts, ids); } + function test_BalanceOfBatch_EmptyArrays() public view { + address[] memory accounts = new address[](0); + uint256[] memory ids = new uint256[](0); + + uint256[] memory balances = facet.balanceOfBatch(accounts, ids); + assertEq(balances.length, 0); + } + + function test_BalanceOfBatch_SameAccountMultipleTimes() public { + facet.mint(alice, TOKEN_ID_1, 100); + facet.mint(alice, TOKEN_ID_2, 200); + + address[] memory accounts = new address[](2); + accounts[0] = alice; + accounts[1] = alice; + + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory balances = facet.balanceOfBatch(accounts, ids); + assertEq(balances[0], 100); + assertEq(balances[1], 200); + } + + function test_BalanceOfBatch_DifferentAccountsSameTokenId() public { + facet.mint(alice, TOKEN_ID_1, 100); + facet.mint(bob, TOKEN_ID_1, 200); + + address[] memory accounts = new address[](2); + accounts[0] = alice; + accounts[1] = bob; + + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_1; + + uint256[] memory balances = facet.balanceOfBatch(accounts, ids); + assertEq(balances[0], 100); + assertEq(balances[1], 200); + } + + function test_BalanceOfBatch_WithZeroAddress() public { + facet.mint(alice, TOKEN_ID_1, 100); + facet.mint(bob, TOKEN_ID_3, 300); + + address[] memory accounts = new address[](3); + accounts[0] = alice; + accounts[1] = address(0); + accounts[2] = bob; + + uint256[] memory ids = new uint256[](3); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + ids[2] = TOKEN_ID_3; + + uint256[] memory balances = facet.balanceOfBatch(accounts, ids); + assertEq(balances[0], 100); + assertEq(balances[1], 0); + assertEq(balances[2], 300); + } + // ============================================ // SetApprovalForAll Tests // ============================================ @@ -152,22 +210,19 @@ contract ERC1155FacetTest is Test { assertFalse(facet.isApprovedForAll(alice, bob)); } - function test_SetApprovalForAll_MultipleOperators() public { - vm.startPrank(alice); - facet.setApprovalForAll(bob, true); - facet.setApprovalForAll(charlie, true); - vm.stopPrank(); - - assertTrue(facet.isApprovedForAll(alice, bob)); - assertTrue(facet.isApprovedForAll(alice, charlie)); - } - function test_RevertWhen_SetApprovalForAllZeroAddress() public { vm.prank(alice); vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidOperator.selector, address(0))); facet.setApprovalForAll(address(0), true); } + function test_SetApprovalForAll_Self() public { + vm.prank(alice); + facet.setApprovalForAll(alice, true); + + assertTrue(facet.isApprovedForAll(alice, alice)); + } + function testFuzz_SetApprovalForAll(address owner, address operator, bool approved) public { vm.assume(owner != address(0) && operator != address(0)); @@ -516,6 +571,14 @@ contract ERC1155FacetTest is Test { facet.mintBatch(alice, ids, amounts); } + function test_MintBatch_EmptyArrays() public { + uint256[] memory ids = new uint256[](0); + uint256[] memory amounts = new uint256[](0); + + facet.mintBatch(alice, ids, amounts); + // Should not revert + } + // ============================================ // Burn Tests (via Harness) // ============================================ @@ -653,6 +716,21 @@ contract ERC1155FacetTest is Test { assertEq(facet.balanceOf(address(receiver), TOKEN_ID_1), 50); } + function test_SafeTransferFrom_ForwardsDataParameter() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None + ); + + facet.mint(alice, TOKEN_ID_1, 100); + + bytes memory data = hex"deadbeef"; + + vm.prank(alice); + vm.expectEmit(true, true, true, false); + emit ERC1155ReceiverMock.Received(alice, alice, TOKEN_ID_1, 50, data, 0); + facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, data); + } + function test_RevertWhen_SafeTransferFrom_ToContractWithWrongReturnValue() public { ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( 0x00c0ffee, // Wrong return value @@ -719,6 +797,46 @@ contract ERC1155FacetTest is Test { assertEq(facet.balanceOf(address(receiver), TOKEN_ID_2), 100); } + function test_SafeBatchTransferFrom_ForwardsDataParameter() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None + ); + + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 50; + amounts[1] = 100; + + facet.mintBatch(alice, ids, amounts); + + bytes memory data = hex"c0ffee"; + + vm.prank(alice); + vm.expectEmit(true, true, true, false); + emit ERC1155ReceiverMock.BatchReceived(alice, alice, ids, amounts, data, 0); + facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, data); + } + + function test_RevertWhen_SafeBatchTransferFrom_ToContractWithRevertNoMessage() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithoutMessage + ); + + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 50; + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(receiver))); + facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, ""); + } + function test_RevertWhen_SafeBatchTransferFrom_ToContractWithWrongReturnValue() public { ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( RECEIVER_SINGLE_MAGIC_VALUE, @@ -757,6 +875,124 @@ contract ERC1155FacetTest is Test { facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, ""); } + function test_RevertWhen_SafeTransferFrom_ToContractWithPanic() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.Panic + ); + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(); + facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, ""); + } + + function test_RevertWhen_SafeBatchTransferFrom_ToContractWithPanic() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.Panic + ); + + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 50; + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(); + facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, ""); + } + + function test_RevertWhen_SafeTransferFrom_ToContractWithCustomError() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.RevertWithCustomError + ); + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_SINGLE_MAGIC_VALUE)); + facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, ""); + } + + function test_RevertWhen_SafeBatchTransferFrom_ToContractWithCustomError() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.RevertWithCustomError + ); + + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 50; + + facet.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_BATCH_MAGIC_VALUE)); + facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, ""); + } + + function test_SafeBatchTransferFrom_EmptyArrays() public { + uint256[] memory ids = new uint256[](0); + uint256[] memory amounts = new uint256[](0); + + vm.prank(alice); + facet.safeBatchTransferFrom(alice, bob, ids, amounts, ""); + // Should not revert + } + + function test_SafeBatchTransferFrom_WithZeroAmounts() public { + uint256[] memory ids = new uint256[](3); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + ids[2] = TOKEN_ID_3; + + uint256[] memory amounts = new uint256[](3); + amounts[0] = 100; + amounts[1] = 200; + amounts[2] = 300; + + facet.mintBatch(alice, ids, amounts); + + uint256[] memory transferAmounts = new uint256[](3); + transferAmounts[0] = 30; + transferAmounts[1] = 0; // Zero amount + transferAmounts[2] = 50; + + vm.prank(alice); + facet.safeBatchTransferFrom(alice, bob, ids, transferAmounts, ""); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(facet.balanceOf(alice, TOKEN_ID_2), 200); + assertEq(facet.balanceOf(alice, TOKEN_ID_3), 250); + assertEq(facet.balanceOf(bob, TOKEN_ID_1), 30); + assertEq(facet.balanceOf(bob, TOKEN_ID_2), 0); + assertEq(facet.balanceOf(bob, TOKEN_ID_3), 50); + } + + function test_SafeBatchTransferFrom_DuplicateTokenIds() public { + facet.mint(alice, TOKEN_ID_1, 100); + + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_1; // Duplicate + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 10; + amounts[1] = 20; + + vm.prank(alice); + facet.safeBatchTransferFrom(alice, bob, ids, amounts, ""); + + assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(facet.balanceOf(bob, TOKEN_ID_1), 30); + } + // ============================================ // Integration Tests // ============================================ diff --git a/test/token/ERC1155/ERC1155/LibERC1155.t.sol b/test/token/ERC1155/ERC1155/LibERC1155.t.sol index b7979dd6..07b39a77 100644 --- a/test/token/ERC1155/ERC1155/LibERC1155.t.sol +++ b/test/token/ERC1155/ERC1155/LibERC1155.t.sol @@ -154,6 +154,72 @@ contract LibERC1155Test is Test { harness.mintBatch(alice, ids, amounts); } + function test_MintBatch_EmptyArrays() public { + uint256[] memory ids = new uint256[](0); + uint256[] memory amounts = new uint256[](0); + + harness.mintBatch(alice, ids, amounts); + // Should not revert + } + + function test_Mint_ToReceiverContract() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None + ); + + harness.mint(address(receiver), TOKEN_ID_1, 100); + assertEq(harness.balanceOf(address(receiver), TOKEN_ID_1), 100); + } + + function test_RevertWhen_Mint_ToReceiverContractWithWrongReturnValue() public { + ERC1155ReceiverMock receiver = + new ERC1155ReceiverMock(0x00c0ffee, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None); + + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidReceiver.selector, address(receiver))); + harness.mint(address(receiver), TOKEN_ID_1, 100); + } + + function test_RevertWhen_Mint_ToReceiverContractWithRevert() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithMessage + ); + + vm.expectRevert("ERC1155ReceiverMock: reverting on receive"); + harness.mint(address(receiver), TOKEN_ID_1, 100); + } + + function test_MintBatch_ToReceiverContract() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None + ); + + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 100; + amounts[1] = 200; + + harness.mintBatch(address(receiver), ids, amounts); + assertEq(harness.balanceOf(address(receiver), TOKEN_ID_1), 100); + assertEq(harness.balanceOf(address(receiver), TOKEN_ID_2), 200); + } + + function test_RevertWhen_MintBatch_ToReceiverContractWithWrongReturnValue() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_SINGLE_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None + ); + + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 100; + + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidReceiver.selector, address(receiver))); + harness.mintBatch(address(receiver), ids, amounts); + } + // ============================================ // Burn Tests // ============================================ @@ -546,6 +612,10 @@ contract LibERC1155Test is Test { assertEq(harness.balanceOf(alice, TOKEN_ID_2), 200); } + function test_BalanceOf_ZeroAddress() public view { + assertEq(harness.balanceOf(address(0), TOKEN_ID_1), 0); + } + // ============================================ // BalanceOfBatch Tests // ============================================ @@ -572,6 +642,68 @@ contract LibERC1155Test is Test { assertEq(balances[2], 300); } + function test_BalanceOfBatch_EmptyArrays() public view { + address[] memory accounts = new address[](0); + uint256[] memory ids = new uint256[](0); + + uint256[] memory balances = harness.balanceOfBatch(accounts, ids); + assertEq(balances.length, 0); + } + + function test_BalanceOfBatch_SameAccountMultipleTimes() public { + harness.mint(alice, TOKEN_ID_1, 100); + harness.mint(alice, TOKEN_ID_2, 200); + + address[] memory accounts = new address[](2); + accounts[0] = alice; + accounts[1] = alice; + + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + + uint256[] memory balances = harness.balanceOfBatch(accounts, ids); + assertEq(balances[0], 100); + assertEq(balances[1], 200); + } + + function test_BalanceOfBatch_DifferentAccountsSameTokenId() public { + harness.mint(alice, TOKEN_ID_1, 100); + harness.mint(bob, TOKEN_ID_1, 200); + + address[] memory accounts = new address[](2); + accounts[0] = alice; + accounts[1] = bob; + + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_1; + + uint256[] memory balances = harness.balanceOfBatch(accounts, ids); + assertEq(balances[0], 100); + assertEq(balances[1], 200); + } + + function test_BalanceOfBatch_WithZeroAddress() public { + harness.mint(alice, TOKEN_ID_1, 100); + harness.mint(bob, TOKEN_ID_3, 300); + + address[] memory accounts = new address[](3); + accounts[0] = alice; + accounts[1] = address(0); + accounts[2] = bob; + + uint256[] memory ids = new uint256[](3); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + ids[2] = TOKEN_ID_3; + + uint256[] memory balances = harness.balanceOfBatch(accounts, ids); + assertEq(balances[0], 100); + assertEq(balances[1], 0); + assertEq(balances[2], 300); + } + // ============================================ // Approval Tests // ============================================ @@ -593,6 +725,13 @@ contract LibERC1155Test is Test { assertFalse(harness.isApprovedForAll(alice, bob)); } + function test_SetApprovalForAll_Self() public { + vm.prank(alice); + harness.setApprovalForAll(alice, true); + + assertTrue(harness.isApprovedForAll(alice, alice)); + } + function testFuzz_SetApprovalForAll(address owner, address operator, bool approved) public { vm.assume(owner != address(0) && operator != address(0)); @@ -686,6 +825,23 @@ contract LibERC1155Test is Test { assertEq(harness.balanceOf(address(receiver), TOKEN_ID_2), 100); } + function test_RevertWhen_SafeBatchTransferFrom_ToContractWithRevertNoMessage() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithoutMessage + ); + + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 50; + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(LibERC1155.ERC1155InvalidReceiver.selector, address(receiver))); + harness.safeBatchTransferFrom(alice, address(receiver), ids, amounts); + } + function test_RevertWhen_SafeBatchTransferFrom_ToContractWithWrongReturnValue() public { ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( RECEIVER_SINGLE_MAGIC_VALUE, @@ -724,6 +880,124 @@ contract LibERC1155Test is Test { harness.safeBatchTransferFrom(alice, address(receiver), ids, amounts); } + function test_RevertWhen_SafeTransferFrom_ToContractWithPanic() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.Panic + ); + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(); + harness.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50); + } + + function test_RevertWhen_SafeBatchTransferFrom_ToContractWithPanic() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.Panic + ); + + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 50; + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(); + harness.safeBatchTransferFrom(alice, address(receiver), ids, amounts); + } + + function test_RevertWhen_SafeTransferFrom_ToContractWithCustomError() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.RevertWithCustomError + ); + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_SINGLE_MAGIC_VALUE)); + harness.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50); + } + + function test_RevertWhen_SafeBatchTransferFrom_ToContractWithCustomError() public { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.RevertWithCustomError + ); + + uint256[] memory ids = new uint256[](1); + ids[0] = TOKEN_ID_1; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 50; + + harness.mint(alice, TOKEN_ID_1, 100); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_BATCH_MAGIC_VALUE)); + harness.safeBatchTransferFrom(alice, address(receiver), ids, amounts); + } + + function test_SafeBatchTransferFrom_EmptyArrays() public { + uint256[] memory ids = new uint256[](0); + uint256[] memory amounts = new uint256[](0); + + vm.prank(alice); + harness.safeBatchTransferFrom(alice, bob, ids, amounts); + // Should not revert + } + + function test_SafeBatchTransferFrom_WithZeroAmounts() public { + uint256[] memory ids = new uint256[](3); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_2; + ids[2] = TOKEN_ID_3; + + uint256[] memory amounts = new uint256[](3); + amounts[0] = 100; + amounts[1] = 200; + amounts[2] = 300; + + harness.mintBatch(alice, ids, amounts); + + uint256[] memory transferAmounts = new uint256[](3); + transferAmounts[0] = 30; + transferAmounts[1] = 0; // Zero amount + transferAmounts[2] = 50; + + vm.prank(alice); + harness.safeBatchTransferFrom(alice, bob, ids, transferAmounts); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(harness.balanceOf(alice, TOKEN_ID_2), 200); + assertEq(harness.balanceOf(alice, TOKEN_ID_3), 250); + assertEq(harness.balanceOf(bob, TOKEN_ID_1), 30); + assertEq(harness.balanceOf(bob, TOKEN_ID_2), 0); + assertEq(harness.balanceOf(bob, TOKEN_ID_3), 50); + } + + function test_SafeBatchTransferFrom_DuplicateTokenIds() public { + harness.mint(alice, TOKEN_ID_1, 100); + + uint256[] memory ids = new uint256[](2); + ids[0] = TOKEN_ID_1; + ids[1] = TOKEN_ID_1; // Duplicate + + uint256[] memory amounts = new uint256[](2); + amounts[0] = 10; + amounts[1] = 20; + + vm.prank(alice); + harness.safeBatchTransferFrom(alice, bob, ids, amounts); + + assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); + assertEq(harness.balanceOf(bob, TOKEN_ID_1), 30); + } + // ============================================ // Integration Tests // ============================================ From 17b4fec42b5b8d0a51b5cb2c566bcc94f926d3a2 Mon Sep 17 00:00:00 2001 From: aapsi Date: Mon, 3 Nov 2025 12:09:34 +0530 Subject: [PATCH 4/4] test: update balance assertions in ERC1155 safeTransferFrom to handle self-transfers --- test/token/ERC1155/ERC1155/LibERC1155.t.sol | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/token/ERC1155/ERC1155/LibERC1155.t.sol b/test/token/ERC1155/ERC1155/LibERC1155.t.sol index 07b39a77..16ef1a27 100644 --- a/test/token/ERC1155/ERC1155/LibERC1155.t.sol +++ b/test/token/ERC1155/ERC1155/LibERC1155.t.sol @@ -410,8 +410,13 @@ contract LibERC1155Test is Test { vm.prank(from); harness.safeTransferFrom(from, to, id, amount); - assertEq(harness.balanceOf(from, id), 0); - assertEq(harness.balanceOf(to, id), amount); + if (from == to) { + // Self-transfer is a no-op; balance remains unchanged + assertEq(harness.balanceOf(from, id), amount); + } else { + assertEq(harness.balanceOf(from, id), 0); + assertEq(harness.balanceOf(to, id), amount); + } } function test_RevertWhen_SafeTransferFromToZeroAddress() public {