From 372df8b80e720556295c93cfa761fa8c7f053424 Mon Sep 17 00:00:00 2001 From: Alex Connolly Date: Fri, 12 Jan 2024 15:34:09 +1100 Subject: [PATCH] Basic Crafting Factory pattern. --- contracts/crafting/Factory.sol | 51 ++++++++++++++++++++ contracts/crafting/IRecipe.sol | 45 +++++++++++++++++ contracts/crafting/examples/SimpleRecipe.sol | 51 ++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 contracts/crafting/Factory.sol create mode 100644 contracts/crafting/IRecipe.sol create mode 100644 contracts/crafting/examples/SimpleRecipe.sol diff --git a/contracts/crafting/Factory.sol b/contracts/crafting/Factory.sol new file mode 100644 index 00000000..4e9693ed --- /dev/null +++ b/contracts/crafting/Factory.sol @@ -0,0 +1,51 @@ +// Copyright Immutable Pty Ltd 2018 - 2023 +// SPDX-License-Identifier: Apache 2.0 +pragma solidity 0.8.19; + +import { IRecipe, ERC1155Input, ERC721Input, ERC20Input, ERC1155Asset } from "./IRecipe.sol"; + +contract Factory { + + uint256 public craftCounter; + + event CraftComplete(uint256 indexed craftID, IRecipe indexed recipe); + + function craft( + IRecipe recipe, + ERC20Input[] calldata erc20Inputs, + ERC721Input[] calldata erc721Inputs, + ERC1155Input[] calldata erc1155Inputs, + bytes calldata data + ) external { + + uint craftID = craftCounter++; + + recipe.beforeTransfers(craftID, erc20Inputs, erc721Inputs, erc1155Inputs, data); + + for (uint i = 0; i < erc20Inputs.length; i++) { + ERC20Input memory input = erc20Inputs[i]; + input.erc20.transferFrom(msg.sender, input.destination, input.amount); + } + + + for (uint i = 0; i < erc721Inputs.length; i++) { + ERC721Input memory input = erc721Inputs[i]; + for (uint j = 0; j < input.tokenIDs.length; j++) { + input.erc721.safeTransferFrom(msg.sender, input.destination, input.tokenIDs[j]); + } + } + + for (uint i = 0; i < erc1155Inputs.length; i++) { + ERC1155Input memory input = erc1155Inputs[i]; + for (uint j = 0; j < input.assets.length; j++) { + ERC1155Asset memory asset = input.assets[j]; + input.erc1155.safeTransferFrom(msg.sender, input.destination, asset.tokenID, asset.amount, "0x0"); + } + } + + recipe.afterTransfers(craftID, data); + + emit CraftComplete(craftID, recipe); + } + +} \ No newline at end of file diff --git a/contracts/crafting/IRecipe.sol b/contracts/crafting/IRecipe.sol new file mode 100644 index 00000000..b5ec8816 --- /dev/null +++ b/contracts/crafting/IRecipe.sol @@ -0,0 +1,45 @@ +// Copyright Immutable Pty Ltd 2018 - 2023 +// SPDX-License-Identifier: Apache 2.0 +pragma solidity 0.8.19; + +import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + + +struct ERC1155Asset { + uint256 tokenID; + uint256 amount; +} + +struct ERC1155Input { + IERC1155 erc1155; + ERC1155Asset[] assets; + address destination; +} + +struct ERC721Input { + IERC721 erc721; + uint256[] tokenIDs; + address destination; +} + +struct ERC20Input { + IERC20 erc20; + uint256 amount; + address destination; +} + +interface IRecipe { + + function beforeTransfers( + uint256 craftID, + ERC20Input[] calldata erc20s, + ERC721Input[] calldata erc721s, + ERC1155Input[] calldata erc1155s, + bytes calldata data + ) external; + + function afterTransfers(uint256 craftID, bytes calldata data) external; + +} \ No newline at end of file diff --git a/contracts/crafting/examples/SimpleRecipe.sol b/contracts/crafting/examples/SimpleRecipe.sol new file mode 100644 index 00000000..60146f86 --- /dev/null +++ b/contracts/crafting/examples/SimpleRecipe.sol @@ -0,0 +1,51 @@ +// Copyright Immutable Pty Ltd 2018 - 2023 +// SPDX-License-Identifier: Apache 2.0 +pragma solidity 0.8.19; + +import { IRecipe, ERC20Input, ERC721Input, ERC1155Input } from "../IRecipe.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +contract SimpleRecipe is IRecipe { + + IERC721 public token; + address private factory; + + constructor(IERC721 _token, address _factory) { + token = _token; + factory = _factory; + } + + modifier onlyFactory { + require(msg.sender == factory, "Caller must be Factory"); + _; + } + + function beforeTransfers( + uint256, + ERC20Input[] calldata erc20s, + ERC721Input[] calldata erc721s, + ERC1155Input[] calldata erc1155s, + bytes calldata + ) external view onlyFactory { + + require(erc20s.length == 0, "No ERC20s allowed."); + require(erc1155s.length == 0, "No ERC1155s allowed."); + require(erc721s.length == 1, "Must be only one ERC721 input."); + + ERC721Input memory input = erc721s[0]; + require(input.erc721 == token, "Must be crafting game assets."); + require(input.destination == address(0), "Only allowed destination is 0x0."); + + // No need to check that the 5 assets are unique as transferring them will fail in the Factory. + + // Can log any events you want + } + + function afterTransfers(uint256 craftID, bytes calldata data) external onlyFactory { + // mint a new NFT to the user + // token.mint() etc. + + // Can log any events you want + } + +} \ No newline at end of file