diff --git a/src/ERC1155Common.sol b/src/ERC1155Common.sol new file mode 100644 index 0000000..2ef2bff --- /dev/null +++ b/src/ERC1155Common.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import {ERC1155Supply} from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; +import {AccessControlEnumerable} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract ERC1155Common is AccessControlEnumerable, ERC1155, ERC1155Supply { + using Strings for uint256; + + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + string private _name; + string private _symbol; + + constructor(string memory name_, string memory symbol_, string memory baseTokenURI, address[] memory minters) + payable + ERC1155(baseTokenURI) + { + _name = name_; + _symbol = symbol_; + _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); + bytes32 minterRole = MINTER_ROLE; + + uint256 length = minters.length; + for (uint256 i; i < length;) { + _grantRole(minterRole, minters[i]); + unchecked { + ++i; + } + } + } + + function uri(uint256 tokenId) public view override returns (string memory) { + string memory _uri = super.uri(tokenId); + return string(abi.encodePacked(_uri, tokenId.toString())); + } + + function name() external view returns (string memory) { + return _name; + } + + function symbol() external view returns (string memory) { + return _symbol; + } + + /** + * @dev Sets the base URI for metadata of ERC1155 tokens. + * @param uri_ The new base URI. + */ + function setURI(string calldata uri_) external onlyRole(DEFAULT_ADMIN_ROLE) { + _setURI(uri_); + } + + /** + * @dev Mints a single ERC1155 token and assigns it to the specified address. + * @param to The address to which the minted token will be assigned. + * @param id The ID of the token to mint. + * @param amount The amount of tokens to mint. + */ + function mint(address to, uint256 id, uint256 amount) external onlyRole(MINTER_ROLE) { + _mint(to, id, amount, ""); + } + + /** + * @dev Mints multiple ERC1155 tokens and assigns them to the specified address. + * @param to The address to which the minted tokens will be assigned. + * @param ids The IDs of the tokens to mint. + * @param amounts The amounts of tokens to mint. + */ + function batchMint(address to, uint256[] calldata ids, uint256[] calldata amounts) external onlyRole(MINTER_ROLE) { + _mintBatch(to, ids, amounts, ""); + } + + function supportsInterface(bytes4 interfaceId) public view override(ERC1155, AccessControlEnumerable) returns (bool) { + return super.supportsInterface(interfaceId); + } + + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual override(ERC1155, ERC1155Supply) { + super._beforeTokenTransfer(operator, from, to, ids, amounts, data); + } +} diff --git a/src/mock/SampleERC1155.sol b/src/mock/SampleERC1155.sol new file mode 100644 index 0000000..878f611 --- /dev/null +++ b/src/mock/SampleERC1155.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import "../ERC1155Common.sol"; + +contract SampleERC1155 is ERC1155Common { + constructor(string memory name, string memory symbol, string memory baseTokenURI, address[] memory minters) + ERC1155Common(name, symbol, baseTokenURI, minters) + {} +} diff --git a/test/foundry/SampleERC1155.t.sol b/test/foundry/SampleERC1155.t.sol new file mode 100644 index 0000000..19b6a92 --- /dev/null +++ b/test/foundry/SampleERC1155.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {SampleERC1155, ERC1155Common} from "../../src/mock/SampleERC1155.sol"; + +contract SampleERC1155Test is Test { + using Strings for uint256; + + string public constant NAME = "SampleERC1155"; + string public constant SYMBOL = "NFT1155"; + string public constant BASE_URI = "http://example.com/"; + address[] public minters = [address(1)]; + + ERC1155Common internal _t; + + function setUp() public virtual { + _t = new SampleERC1155(NAME, SYMBOL, BASE_URI, minters); + } + + function testName() public virtual { + assertEq(_token().name(), NAME); + } + + function testSymbol() public virtual { + assertEq(_token().symbol(), SYMBOL); + } + + function testURI(address _from) public virtual { + vm.assume(_from.code.length == 0 && _from != address(0)); + assertEq(_token().uri(uint256(50)), string(abi.encodePacked(BASE_URI, uint256(50).toString()))); + } + + function testMint() public virtual { + vm.startPrank(address(1)); + _token().mint(address(15), 15, 15); + assertEq(_token().totalSupply(15), 15); + assertEq(_token().balanceOf(address(15), 15), 15); + + _token().mint(address(20), 15, 15); + assertEq(_token().totalSupply(15), 30); + assertEq(_token().balanceOf(address(20), 15), 15); + vm.stopPrank(); + } + + function _token() internal view virtual returns (ERC1155Common) { + return _t; + } +}