From 11d70f99d0ad69ab23afdf119869a7062f67b32a Mon Sep 17 00:00:00 2001 From: wwhchung Date: Tue, 9 Apr 2024 07:03:43 -0700 Subject: [PATCH] Adds single mint contract guards (#83) * fix tests * refactor common code * revert private * add scripts, fix tests * add 1155 single mint * make it an admin mint guard * fix mint method --- .../single/IManifoldERC1155Single.sol | 18 +++++ .../single/IManifoldERC721Single.sol | 18 +++++ .../single/ManifoldERC1155Single.sol | 35 ++++++++++ .../contracts/single/ManifoldERC721Single.sol | 33 +++++++++ .../script/ManifoldERC1155Single.s.sol | 17 +++++ .../script/ManifoldERC721Edition.s.sol | 4 +- .../script/ManifoldERC721Single.s.sol | 17 +++++ .../test/single/ManifoldERC1155Single.t.sol | 70 +++++++++++++++++++ .../test/single/ManifoldERC721Single.t.sol | 62 ++++++++++++++++ 9 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 packages/manifold/contracts/single/IManifoldERC1155Single.sol create mode 100644 packages/manifold/contracts/single/IManifoldERC721Single.sol create mode 100644 packages/manifold/contracts/single/ManifoldERC1155Single.sol create mode 100644 packages/manifold/contracts/single/ManifoldERC721Single.sol create mode 100644 packages/manifold/script/ManifoldERC1155Single.s.sol create mode 100644 packages/manifold/script/ManifoldERC721Single.s.sol create mode 100644 packages/manifold/test/single/ManifoldERC1155Single.t.sol create mode 100644 packages/manifold/test/single/ManifoldERC721Single.t.sol diff --git a/packages/manifold/contracts/single/IManifoldERC1155Single.sol b/packages/manifold/contracts/single/IManifoldERC1155Single.sol new file mode 100644 index 00000000..ecb616a8 --- /dev/null +++ b/packages/manifold/contracts/single/IManifoldERC1155Single.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @author: manifold.xyz + +/** + * Manifold ERC1155 Single Mint interface + */ +interface IManifoldERC1155Single { + + error InvalidInput(); + + /** + * @dev Mint a new token + */ + function mint(address creatorCore, uint256 expectedTokenId, string calldata uri, address[] calldata recipients, uint256[] calldata amounts) external; +} diff --git a/packages/manifold/contracts/single/IManifoldERC721Single.sol b/packages/manifold/contracts/single/IManifoldERC721Single.sol new file mode 100644 index 00000000..03e9e104 --- /dev/null +++ b/packages/manifold/contracts/single/IManifoldERC721Single.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @author: manifold.xyz + +/** + * Manifold ERC721 Single Mint interface + */ +interface IManifoldERC721Single { + + error InvalidInput(); + + /** + * @dev Mint a token + */ + function mint(address creatorCore, uint256 expectedTokenId, string calldata uri, address recipient) external; +} diff --git a/packages/manifold/contracts/single/ManifoldERC1155Single.sol b/packages/manifold/contracts/single/ManifoldERC1155Single.sol new file mode 100644 index 00000000..a249aceb --- /dev/null +++ b/packages/manifold/contracts/single/ManifoldERC1155Single.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @author: manifold.xyz + +import "@manifoldxyz/libraries-solidity/contracts/access/IAdminControl.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/core/IERC1155CreatorCore.sol"; +import "./IManifoldERC1155Single.sol"; + +/** + * Manifold ERC1155 Single Mint Implementation + */ +contract ManifoldERC1155Single is IManifoldERC1155Single { + + /** + * @dev Only allows approved admins to call the specified function + */ + modifier creatorAdminRequired(address creator) { + if (!IAdminControl(creator).isAdmin(msg.sender)) revert("Must be owner or admin of creator contract"); + _; + } + + /** + * @dev See {IManifoldERC1155Single-mintNew}. + */ + function mint(address creatorCore, uint256 expectedTokenId, string calldata uri, address[] calldata recipients, uint256[] calldata amounts) external override creatorAdminRequired(creatorCore) { + string[] memory uris = new string[](1); + uris[0] = uri; + uint256[] memory tokenIds = IERC1155CreatorCore(creatorCore).mintBaseNew(recipients, amounts, uris); + if (tokenIds.length != 1 || tokenIds[0] != expectedTokenId) { + revert InvalidInput(); + } + } +} diff --git a/packages/manifold/contracts/single/ManifoldERC721Single.sol b/packages/manifold/contracts/single/ManifoldERC721Single.sol new file mode 100644 index 00000000..b8e2aedb --- /dev/null +++ b/packages/manifold/contracts/single/ManifoldERC721Single.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @author: manifold.xyz + +import "@manifoldxyz/libraries-solidity/contracts/access/IAdminControl.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/core/IERC721CreatorCore.sol"; +import "./IManifoldERC721Single.sol"; + +/** + * Manifold ERC721 Single Mint Implementation + */ +contract ManifoldERC721Single is IManifoldERC721Single { + + /** + * @dev Only allows approved admins to call the specified function + */ + modifier creatorAdminRequired(address creator) { + if (!IAdminControl(creator).isAdmin(msg.sender)) revert("Must be owner or admin of creator contract"); + _; + } + + /** + * @dev See {IManifoldERC721Single-mint}. + */ + function mint(address creatorCore, uint256 expectedTokenId, string calldata uri, address recipient) external override creatorAdminRequired(creatorCore) { + uint256 tokenId = IERC721CreatorCore(creatorCore).mintBase(recipient, uri); + if (tokenId != expectedTokenId) { + revert InvalidInput(); + } + } +} diff --git a/packages/manifold/script/ManifoldERC1155Single.s.sol b/packages/manifold/script/ManifoldERC1155Single.s.sol new file mode 100644 index 00000000..6d2ae6de --- /dev/null +++ b/packages/manifold/script/ManifoldERC1155Single.s.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import "../contracts/single/ManifoldERC1155Single.sol"; + +contract DeployManifoldERC1155Single is Script { + function run() external { + // uint256 deployerPrivateKey = pk; // uncomment this when testing on goerli + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); // comment this out when testing on goerli + vm.startBroadcast(deployerPrivateKey); + // forge script script/ManifoldERC1155Single.s.sol --optimizer-runs 1000 --rpc-url --broadcast + // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 1000 --chain sepolia contracts/single/ManifoldERC1155Single.sol:ManifoldERC1155Single --watch + new ManifoldERC1155Single{salt: 0x4d616e69666f6c644552433131353553696e676c654d616e69666f6c64455243}(); + vm.stopBroadcast(); + } +} diff --git a/packages/manifold/script/ManifoldERC721Edition.s.sol b/packages/manifold/script/ManifoldERC721Edition.s.sol index 3f41f41d..2d9ac5fb 100644 --- a/packages/manifold/script/ManifoldERC721Edition.s.sol +++ b/packages/manifold/script/ManifoldERC721Edition.s.sol @@ -9,8 +9,8 @@ contract DeployManifoldERC721Edition is Script { // uint256 deployerPrivateKey = pk; // uncomment this when testing on goerli uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); // comment this out when testing on goerli vm.startBroadcast(deployerPrivateKey); - // forge script scripts/ManifoldERC721Edition.s.sol --optimizer-runs 1000 --rpc-url --broadcast - // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 1000 --chain sepolia contracts/edition/ManifoldERC721Edition.sol:ManifoldERC721Edition --constructor-args $(cast abi-encode "constructor(address)" "${INITIAL_OWNER}") --watch + // forge script script/ManifoldERC721Edition.s.sol --optimizer-runs 1000 --rpc-url --broadcast + // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 1000 --chain sepolia contracts/edition/ManifoldERC721Edition.sol:ManifoldERC721Edition --watch new ManifoldERC721Edition{salt: 0x4d616e69666f6c6445524337323145646974696f6e4d616e69666f6c64455243}(); vm.stopBroadcast(); } diff --git a/packages/manifold/script/ManifoldERC721Single.s.sol b/packages/manifold/script/ManifoldERC721Single.s.sol new file mode 100644 index 00000000..ac1eaa66 --- /dev/null +++ b/packages/manifold/script/ManifoldERC721Single.s.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import "../contracts/single/ManifoldERC721Single.sol"; + +contract DeployManifoldERC721Single is Script { + function run() external { + // uint256 deployerPrivateKey = pk; // uncomment this when testing on goerli + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); // comment this out when testing on goerli + vm.startBroadcast(deployerPrivateKey); + // forge script script/ManifoldERC721Single.s.sol --optimizer-runs 1000 --rpc-url --broadcast + // forge verify-contract --compiler-version 0.8.17 --optimizer-runs 1000 --chain sepolia contracts/single/ManifoldERC721Single.sol:ManifoldERC721Single --watch + new ManifoldERC721Single{salt: 0x4d616e69666f6c6445524337323153696e676c654d616e69666f6c6445524337}(); + vm.stopBroadcast(); + } +} diff --git a/packages/manifold/test/single/ManifoldERC1155Single.t.sol b/packages/manifold/test/single/ManifoldERC1155Single.t.sol new file mode 100644 index 00000000..5bb8c823 --- /dev/null +++ b/packages/manifold/test/single/ManifoldERC1155Single.t.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../../contracts/single/ManifoldERC1155Single.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/ERC1155Creator.sol"; + +import "../mocks/Mock.sol"; + +contract ManifoldERC1155SingleTest is Test { + ManifoldERC1155Single public example; + ERC1155Creator public creatorCore; + + address public owner = 0x6140F00e4Ff3936702E68744f2b5978885464cbB; + address public operator = 0xc78Dc443c126af6E4f6Ed540c1e740C1b5be09cd; + + address public zeroAddress = address(0); + address public deadAddress = 0x000000000000000000000000000000000000dEaD; + uint256 private constant MAX_UINT_256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + + function setUp() public { + vm.startPrank(owner); + creatorCore = new ERC1155Creator("Token", "NFT"); + + example = new ManifoldERC1155Single(); + + creatorCore.registerExtension(address(example), ""); + vm.deal(owner, 10 ether); + vm.deal(operator, 10 ether); + vm.stopPrank(); + } + + function testAccess() public { + address[] memory recipients = new address[](1); + uint256[] memory amounts = new uint256[](1); + vm.startPrank(operator); + vm.expectRevert("Must be owner or admin of creator contract"); + example.mint(address(creatorCore), 1, "", recipients, amounts); + vm.stopPrank(); + } + + function testMint() public { + vm.startPrank(owner); + creatorCore.approveAdmin(address(example)); + address[] memory recipients = new address[](1); + uint256[] memory amounts = new uint256[](1); + recipients[0] = operator; + amounts[0] = 2; + example.mint(address(creatorCore), 1, "", recipients, amounts); + assertEq(creatorCore.balanceOf(operator, 1), 2); + // Can't mint same instance twice + vm.expectRevert(IManifoldERC1155Single.InvalidInput.selector); + example.mint(address(creatorCore), 1, "", recipients, amounts); + vm.stopPrank(); + } + + function testTokenURI() public { + vm.startPrank(owner); + creatorCore.approveAdmin(address(example)); + address[] memory recipients = new address[](1); + uint256[] memory amounts = new uint256[](1); + recipients[0] = operator; + amounts[0] = 2; + example.mint(address(creatorCore), 1, "https://arweave.net/1hRadwN29sN5UDl_BBgH4RhCc2TjknMpuzGsP1t3wEM", recipients, amounts); + assertEq(creatorCore.uri(1), "https://arweave.net/1hRadwN29sN5UDl_BBgH4RhCc2TjknMpuzGsP1t3wEM"); + vm.stopPrank(); + } + +} diff --git a/packages/manifold/test/single/ManifoldERC721Single.t.sol b/packages/manifold/test/single/ManifoldERC721Single.t.sol new file mode 100644 index 00000000..28e40781 --- /dev/null +++ b/packages/manifold/test/single/ManifoldERC721Single.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../../contracts/single/ManifoldERC721Single.sol"; +import "@manifoldxyz/creator-core-solidity/contracts/ERC721Creator.sol"; + +import "../mocks/Mock.sol"; + +contract ManifoldERC721SingleTest is Test { + ManifoldERC721Single public example; + ERC721Creator public creatorCore; + + address public owner = 0x6140F00e4Ff3936702E68744f2b5978885464cbB; + address public operator = 0xc78Dc443c126af6E4f6Ed540c1e740C1b5be09cd; + + address public zeroAddress = address(0); + address public deadAddress = 0x000000000000000000000000000000000000dEaD; + uint256 private constant MAX_UINT_256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + + + function setUp() public { + vm.startPrank(owner); + creatorCore = new ERC721Creator("Token", "NFT"); + + example = new ManifoldERC721Single(); + + creatorCore.registerExtension(address(example), ""); + vm.deal(owner, 10 ether); + vm.deal(operator, 10 ether); + vm.stopPrank(); + } + + function testAccess() public { + vm.startPrank(operator); + vm.expectRevert("Must be owner or admin of creator contract"); + example.mint(address(creatorCore), 1, "", address(0)); + vm.stopPrank(); + } + + function testMint() public { + vm.startPrank(owner); + creatorCore.approveAdmin(address(example)); + example.mint(address(creatorCore), 1, "", operator); + assertEq(creatorCore.balanceOf(operator), 1); + // Can't mint same instance twice + vm.expectRevert(IManifoldERC721Single.InvalidInput.selector); + example.mint(address(creatorCore), 1, "", operator); + vm.stopPrank(); + } + + function testTokenURI() public { + vm.startPrank(owner); + creatorCore.approveAdmin(address(example)); + example.mint(address(creatorCore), 1, "https://arweave.net/1hRadwN29sN5UDl_BBgH4RhCc2TjknMpuzGsP1t3wEM", operator); + assertEq(creatorCore.balanceOf(operator), 1); + assertEq(creatorCore.tokenURI(1), "https://arweave.net/1hRadwN29sN5UDl_BBgH4RhCc2TjknMpuzGsP1t3wEM"); + vm.stopPrank(); + } + +}