Giter Site home page Giter Site logo

contracts's Introduction

Owl Protocol

contracts workflow web3-redux workflow

Owl Protocol - Web3 Tools & Dynamic NFTs.

TL;DR

git clone --recursive [email protected]:owlprotocol/owlprotocol.git

If you are using Turbo - OPTIONAL but recommended for devs

npx turbo login
npx turbo link

Then:

pnpm i

Packages

Package Version Description
Smart Contracts
contracts contracts-npm Dynamic NFT smart contractssuch as Crafting, Breeding, and other complex smart contract mechanics.
State Management
crud-redux crud-redux-npm CRUD state management library with caching using Redux and Dexie.js
web3-redux web3-redux-npm Web3 state management library with caching using web3.js, Redux, and Dexie.js
web3-redux-components web3-redux-components-npm React UI Components for common Web3 smart contracts such as EIP-20, EIP-721, and EIP-1155 as well as more complex Dynamic NFT mechanics.
NFT Metadata Encoding
nft-sdk nft-sdk-npm NFT SDK for encoding/decoding on-chain dna.
nft-sdk-components nft-sdk-components-npm React UI Components to render decoded attribtues.

LICENSE

Packages have different licenses depending on their nature & forks.

  • @owlprotocol/contracts-api: All Rights Reserved. (More opensource license under research)
  • @owlprotocol/chains: Apache-2. Forked from @thirdweb-dev/chains which is Apache-2.
  • All other packages: MIT License

Docs

Read the official docs at docs.owlprotocol.xyz.

contracts's People

Stargazers

 avatar  avatar  avatar

contracts's Issues

`uint256` DNA breeding

Work on a DNA breeding algorithm that just uses Solidity primitives. Create a Library and a testing smart contract to expose the library methods.
The function signature is as follows:
breedDNASimple(uint256[] parents, uint8[] genes, uint256 randomness) returns (uint256)
The parameters correspond to the following:

  • parents: N different parent DNAs
  • genes: the start indices of each "gene", this splits the 256bit DNA into different sections. Note that indices MUST be passed in increasing order (don't verify but assume developer has properly configured this). uint8 is used as positions 0-255 are valid. In total, the DNA is split into genes.length genes. As a best practice the first index SHOULD be 0. For example, [0, 128] splits the DNA into 2 genes of equal length, gene1 covering bits 0-127, gen2 128-255.
  • randomness: A randomness seed that is used for pseudo-random number generation by iteratively hashing

Conceptually DNA breeding algorithm treats all genes equi-probable meaning there is no concept of dominant/recessive genes. Genes are simply picked at random among the parent's features. On a high-level, we pick a random parent, splice the gene using a bit mask, and repeat for each section of the DNA. Finally we recombine all selected genes to create the final child DNA.

The algorithm works as follows:

uint256[] selectedGenes = []
//end index non-inclusive, like String.substring
//end index is at genes[i+1], or 256 for the last gene
For EACH [geneStartIdx, geneEndIdx] IN genes[]:
    //Make sure to salt randomness with array idx
    uint8 randomParentIdx = keccak256(randomness+i) mod parents.length
    uint256 selectedParent = parents[randomParentIdx]

    uint256 bitMaskStart = uint(-1) >> geneStartIdx  //0x00FFFF     Cuts out anything before start of gene
    uint256 bitMaskEnd = uint(-1) << geneEndIdx      //0xFFFF00     Cuts out anything after end of gene
    uint256 bitMask = bitMaskStart ^ bitMaskEnd      //0x00FF00     Final bitmask

    uint256 gene = selectedParent ^ bitMask //Final selected gene
    selectedGenes.push(gene)

//Recombine genes
uint256 childDNA = 0;
For EACH gene IN selectedGenes:
    childDNA = childDNA | gene;

return childDNA;

Start index is inclusive, end index is not. Similar concept to below
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring

Also recommended is a quick refresher on bit-shifting and bitmasks.
https://en.wikipedia.org/wiki/Mask_(computing)
https://docs.soliditylang.org/en/latest/types.html?highlight=bitwise#shifts

As a sidenote the bitMask process is similar to the "annealing" phase of PCR.
https://en.wikipedia.org/wiki/Polymerase_chain_reaction

Monorepo Refactor

Break project into sub-packages for better npm packaging.

  • nft-launcher-contracts-sol: Package Solidity contract details

    • Typechain
    • ABIs
    • Contracts
  • nft-laucnher-contracts-lib

    • Image merging

`RosalindDNA` Generation Counter

Description

Many breeding-based NFT projects have a generation property for their NFT. This value refers to how many ascendants the NFT has and corresponds to the max parent gen + 1. Because the breeding mechanic introduces user-controlled dynamic supply mechanics, the generation property counteracts dilution effects by creating a tiered system of generations. Generations are often then used to impact the quality of an NFT by influencing properties such as stats, cool-downs, or breeding fees.

For info on related proejcts that use NFT gen counters. Check out these links:

Features

For this initial implementation, we will not concern ourselves with the logic that effects game mechanics dependent on generation count. All we're looking to do is compute and set the generation count of a descendant and integrate this logic as part of the NFTBreederLibrary for maximum portability. We want to have the generation encoded within a NFT's DNA.

Create a function with the following signature similar to previous breeding algos:
breedDNAGenCount(uint256 child, uint256[] parents) returns (uint256)
The parameters correspond to the following:

  • child: The original child DNA. Assume this was computed with one of the breeding algorithms.
  • parents: The parent DNAs used to generate this child. We use these to get parents' gen count and find the max.

For standardization, if there is a gen counter attribute, it is ALWAYS encoded as a the lower 8 bits of the DNA (uint8). This means that up to 256 generations are supported (0-255).
The setDNAGenCount logic will be as follows:

  • Loop through each parent
    • Use a bitmask to extract the parent DNA
    • Compare to current maximum
  • If parentMax >= uint8(-1) throw (Max gen count 255 reached).
  • Compute newGen = parentMax + 1
  • Set lower 8 bits of child as newGen
  • Return updated child DNA
    Also consider creating a simple utility function getGenCount(dna) that returns uint8 gen count. This should only be used by 3rd party devs that don't want to use a bitmask.

CloneFactory Proxy

Since the platform will be launching the same contract over and over again, use of a minimal proxy contract (as directed by eip 1167) would be optimal as it would greatly reduce gas costs for launching each contract.
Note: This will slightly increase the gas cost of each transaction.

How It Works

The code for the contract is only deployed once. Every instance of the contract that is to be used by users will be done through a proxy. All calls from the user will be made to the proxy and the proxy will forward all calls to the code instance (the slightly more expensive gas cost comes from this extra routing step). All execution will take place in the context of the proxy

Implementation (of Factory Contract)

Refactor project

Refactor the nft-launcher-contracts project.

  • assets/ => crypto asset contracts
  • plugins/ => game mechanic plugins

Create proxy-based deploy scripts for:

  • ProxyFactory
  • ERC20
  • ERC721
  • ERC1155

`BatchTransfer.sol` unit tests

Description

There are no tests currently dedicated to testing the backend library contract BatchTransfer.sol. It doesn't need to be complex, but I think the project would benefit from tests covering these functions.

Implementation

Simply create a dummy interface contract under contracts/Testing called BatchTransferTest.sol and use that to test the batch transfer functions with ethersjs.

`uint256` DNA breeding with mutations

Make sure to first implement the related issue #27 first
Similar to the previous issue, add the breedDNAWithMutations function to the same library and test accordingly.

DNA breeding with mutations algorithm is similar to the breedDNASimple but with a twist. Conceptually, each gene has a uint256 mutation parameter which determines a rate at which parent DNA will be ignored and instead random bits, a mutated gene will be assigned to the child gene. Note that it is possible that the random bits correspond to one of the parent genes.

A higher mutation rate means a higher probability of having a random gene. The mutation rate m is a probability of m/(2^256-1) or in other words the probability that geneRandomnessSeed <= m. We therefore assign a mutated gene at the following rates according to m:

  • 0 = 000... => 0%
  • 2^254-1 = 001... => 25%
  • 2^255-1 = 011... => 50%
  • 2^255 + 2^254 -1 => 75%
  • 2^256-1 = 111... => 100% Always mutate
    zeroes 256-m the gene random seed MUST have to to trigger a mutation. Mathematically, this gives us a probability of 1/2^(256-m). We can simply check this with geneRandomSeed <= (2^256-m). Note that EACH gene has a mutation parameter so certain attributes might be more mutagenic than others.

The function signature is as follows:
breedDNAWithMutations(uint256[] parents, uint8[] genes, uint256 randomness, uint256[] mutationRates) returns (uint256)

  • mutationRates: probability that a random gene is picked

The algorithm then works similarly to the breedDNASimple:

uint256[] selectedGenes = []
//end index non-inclusive, like String.substring
//end index is at genes[i+1], or 256 for the last gene
For EACH i < genes.length:
     uint256 selectedGene;
      //Make sure to salt randomness with array idx
     uint256 geneRandomnessSeed = keccak256(randomness+i)
     if (geneRandomnessSeed <= mutationRates[i]) selectedGene = keccak256(geneRandomnessSeed) //random bits
     else selectedGene = parents[geneRandomnessSeed % parents.length] //parent bits

    //Bitmask etc...

Contract : Locked Consumable Type

Locked (aka Equip): This would be for an input that can be recovered later, but cannot be re-used so long as the output exists. As an example, a “character” + “sword” NFT ⇒ “character equipped with sword” NFT. Both inputs would be locked until the sword is unequipped.

Contract : Input ERC721 extra rules

  • ids: uint256[] array of valid input ERC721 ids. Note that each craft actions only requires 1 instance that is from within the array of valid ids. Add additional ERC721 inputs if a craft recipe requires multiple inputs.
  • idRange: uint256[2] [min,max] id range of valid input ERC721 ids
  • idMerkleRoot: Merkle root of valid id set

`Crafter` updates

Couple suggestions after my review of the NFTCrafter contract.

  • Add a function createRecipeWithDeposit which combines createRecipe & depositForRecipe arguments, first calling createForRecipe and then depositForRecipe just to enable creating a recipe + deposit in one transaction without the need of setting up a proxy contract
  • Use check-effects-interactions pattern to mitigate any re-entrancy risks (notably any updates to craftableAmount and craftedAmount should be made BEFORE any token transfer, usually right AFTER the require statement.
  • Use revert(Message) instead of assert(false) revert
  • Make _recipes mapping private (default is public, but we have our custom getter getRecipe)

Style suggestions

  • Rename the contract to simply Crafter/ICrafter as it can mint ERC721, ER20 so might be confusing if we refer to it as "NFT" crafter even though it supports different types of assets.
  • Typescript Array<type> can be written shorthand type[]
  • Compact your if/else statements with comments as follow and avoid extra empty lines
if (statement) {
    //This is the comment for the true case
} else {
    //This is the comment for the false case
}

`EIP1820` Support

Description

As the web app will be multichain and support an dynamic list of smart contracts, we cannot simply hardcode a list of supported addresses. Instead we should aim to be compatible with the EIP1820 standard which defines and global on-chain registry. This registry is already deployed on-chain, but for testing purposes you can also consider using the published source implementation to deploy on local tests.

Links

Before you get started, read up on the standard and deployed implementation using the following resources:

Features

To support the registry function we will need the following:

  • Have all deployable (not library) contracts implement IERC165 or IERC1820Implementer. It seems the registry is compatible with both but research the notable differences first to determine which one (or both) our contracts should support. Note that to avoid duplicate code you may want to create an abstract contract that other contracts inherit from.
  • Copy ERC1820Registry source for testing
  • Create unit tests that deploys a registry, deploys a contract, and then uses the registry to get that contract's address and interface. To achieve this you will need to listen to events emitted by the registry.

Conclusion

Once complete, we should be able to achieve the following:

  • Hard-code 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24 as the registry address (or whatever test address registry deployed to)
  • Derive all Owl NFT Launcher related contracts by filtering by manager address and get interfaceHash. These are then used to populate different parts of the web app.

`Minter` Allowlist

Basic Features

WhitelistSimple: Whitelist implemented as on-chain mapping.
WhitelistMerkle: Whitelist implemented as merkle root.
WhitelistLimited: Limited amount of calls allowed allowed.

Use openzeppelin implementation to verify merkle proofs.

Simple whitelisting is done through MinterCore contract, stores 2d mapping (maps species->[address:whitelistedBool]:

function isWhitelisted(species, address) returns (bool): Runs whitelist checks

function whitelistAddress(species, address): Adds an address to the registry

Whitelist functionality

onlyWhitelisted(speciesId, user) modifier called before mint/safeMint:

  • Checks simple whitelist
  • Calls external whitelist contract (if set)

Extension functionality

  • Allows user to add custom extended-whitelisting contracts
  • Allows user to add template extended-whitelisting contracts (merkle trees, countdown whitelist, etc)

Extension whitelist contracts are added to a dynamic list of access control contracts, they're called sequentially to confirm user is allowed to mint.

Contracts MUST implement the following:

interface IMinterAccessControl {

    function isUserAllowed(uint256 speciesId, address user) external returns (bool);

}

Hardhat Parallel Testing (low priority)

Parallel testing would speed up tests significantly but currently runs into issues.

It appears certain tests, when run out of order are failing due to permission (contract access control) issues. These need to be investigated and tests likely refactored in order to support parallel testing.

Suggested workaround: utilize .only when developing a specific test file(s). This allows tests to be ran without running the entire suite. Once a case(s) is working, remove the .only and test the entire suite.

`Breeder` cooldown

Description

To curb the inflationary dynamics introduced by the NFT breeding mechanic, some games have built-in cooldowns which prevent a parent to breed again for a certain period of time. Introduce such logic to a Breeder contract.

Features

  • Set cooldown: Cooldown time in seconds
  • Track tokenId => lastBredAt
  • When parents breed
    • Check lastBredAt + cooldown < block.timestamp
    • Update lastBredAt for each parent

Discussion: GameFi & P2E Contract Ideas

General Contract Discussion

What are some interesting features we could look to implement beyond general interactive NFT capabilities?
Would these require additional smart contracts?

GameFi

GameFi refers the intersection of gaming and finance. This can be play to earn but also other decentralized finance logic such as vesting, staking or trading.
Some contract ideas:

  • Token Vesting
  • P2E
  • Staking
  • Payment Streaming

Research

Consider researching existing protocols and their different tradeoffs.

NFT Specie Metadata Validator & Typeguard

Problem

Currently, the metadata encode/decode assumes that the passed spec is well defined using the Typescript interfaces. However, this might not be the case, whether it be due to user error somewhere else in the stack or because an API responded with incorrect data. Coercing the any type into the SpecieMetadata interface is a recipe for disaster and we need a solution to properly validate json inputs as corresponding the nft-specie.schema.json spec.

Solution

Regardless, we cannot simply assume that a json payload conforms to the spec.
To resolve this problem implement the following user-defined typeguard that validates an any payload to be SpecieMetadata:

Relevant Links

`RosalindDNA` Refactor mutations logic as standalone

Description

To avoid duplicate logic refactor the breedDNAWithMutations logic to only include mutation logic. Because the mutation logic does not require the parent DNA, the function signature will no longer take the parents as input but simply take the child DNA and mutate it.
This refactor comes at a slight gas cost increase but benefits from removing code duplication with regards to the breeding logic. Now a user will simply call breedDNAWithParents and then introduce mutations with breedDNAWithMutations. Logic for breeding parent DNA and introducing mutations is therefore isolated into individual functions.

Features

Update function signature as follows:
breedDNAWithMutations(uint256 child, uint8[] genes, uint256 randomness, uint256[] mutationRates)
The only difference with the previous function signature is that instead of taking parents as the source DNA, we simply take a single child DNA as source DNA.

The logic for the function is very similar to existing implementation. Some key differences:

  • Take as input the child DNA
  • For each gene
    • If mutation occurred update child gene with randomness
    • Else do nothing
  • Return updated newChild

`Breeder` cooldown by generation

Description

As mentioned in #48 and #50 , to curb inflation introduced by breeding, NFT projects often create a tiered generation system and introduce breeding cooldown mechanics. These two solutions can also be combined to make new generations decreasingly valuable by tying the cooldown time to increasing generation count.

Research

Spend some time researching what mathematical formula should be used to compute cooldownForGen(x). Should it be linear? Exponential? What solidity libraries exist for more complex non-linear functions?

Features

Most of the logic will be similar to #50 except for the fact that instead of having a fixed cooldown, the cooldown is computed as cooldownForGen(x).

  • Set cooldown formula parameters. This should enable a cooldownForGen(x) function.
  • Decode parent gen
  • Set parent cooldowns

CloneFactory universal with address.call(data) initialization

The Clone factory currently supports deploying EIP1167 Proxies. These proxies point to an implementation contract and use DELEGATE ALL to re-use the same logic. Because of this architecture however, implementation contracts CANNOT have a constructor.
The solution to this problem is to use the initializer pattern. An initializer is a function that can only be called once and that is usually called at deployment time atomically within the same transaction. https://docs.openzeppelin.com/contracts/3.x/upgradeable

Update the CloneFactory contract to have a data argument which will be used to make a call to the deployed proxy. If the data's length > 0, use addr.call() to send the encoded initialization call.

https://ethereum.stackexchange.com/questions/96685/how-to-use-address-call-in-solidity

As an example, update this

   function clone(address implementation) public returns (address instance) {

To

    function clone(address implementation, bytes calldata initializeCallData) external returns (address instance) {

Then edit the logic post deployment and insert

deployAddress.call(initializeCallData)

This factory contract should enable us to deploy any implementation contract for cheap by avoiding to redeploy complex logic.

`BatchMint.sol` unit tests

Description

There are no tests currently dedicated to testing the backend library contract BatchMint.sol. It doesn't need to be complex, but I think the project would benefit from tests covering these functions.

Implementation

Simply create a dummy interface contract under contracts/Testing called BatchMintTest.sol and use that to test the batch transfer functions with ethersjs.

NFT Specie Metadata Bugfix

Some suggestions to improve the codebase:

  • Replace custom padding function with Web3.utils.padLeft or Web3.utils.padRight
  • Replace OOP patterns in favor of functional solution
    • Creating a class is probably overkill here and most of the similar functionality can be achieved by simple function parameters
    • Having pure functions makes them idempodent (same input always gives same output) and easier to compose

`ERC721Mintable` base NFT contract

As mentioned in #39 our architecture will rely on a standard NFT preset that is then plug-gable into various mint strategies. Implement this standard ERC721Mintable with the following tools:

  • https://docs.openzeppelin.com/contracts/4.x/wizard (Use this to quickly generate the code that you will then save as ERC721Mintable.sol
  • Check Features/Mintable (no AutoId)
  • Check Access Control/Roles
  • Make the _baseURI() settable, this makes it possible for the developer to quickly update the NFT metadata
    • create a baseURI public string variable
    • override _baseURI() to return baseURI (for an example on this input a value the URI field in the Wizard)
    • add a setBaseURI(baseURI_) function to set the baseURI with ADMIN privileges
  • Update the constructor to take name_, symbol_, baseURI_ as parameters
    • Call parent constructor ERC721(name_, symbol_)
    • Set baseURI = baseURI_
    • Grant roles (as in wizard)
  • Expose the mint function simliar to how the wizard exposes safeMint: This is just meant for the possible situation where a smart contract that doesn't implement the receiver interface still want to get an NFT. You SHOULD look to use safeMint wherever possible in your own code.

We should now have an NFT contract with the following features

  • Settable baseURI
  • mint/safeMint support that we can delegate Access Control roles

Contract : NFTContractLibrary.sol

Library for common data structures.

Enums

  • ConsumableType

Categorize inputs by how they should be affected by transformation.

  • 0 = Unaffected
  • 1 = Burned

Structs

  • RecipeInputERC20

  • address: input contract address

  • consumableType: ConsumableType

  • amount: uint256 for required amount

  • RecipeInputERC721

Input ERC721, initially any input from the specified contract is deemed valid.

  • address: input contract address

  • consumableType: ConsumableType

  • RecipeOutputERC20

  • address: input contract address

  • amount: uint256 amount minted

  • RecipeOutputERC721

  • address: input contract address

  • ids: uint256[] ids for the set of output ERC721. This serves similar to a mint() function, iterating through the array from index 0 for each new craft action by using the craftedAmount

  • Recipe

The crafting recipe is at the core of the NFTCrafter contract. It enables developers to define a transformation rule from N inputs to M outputs.

  • owner: address creator of crafting recipe
  • InputsERC20 RecipeInputERC20[]: Array of recipe ERC20 inputs. User must call .approve(,amount) for each token input before attempting to craft as transaction will fail otherwise.
  • InputsERC721 RecipeInputERC721[]: Array of recipe ERC721 inputs. User must call .approve(,id) for each NFT input before attempting to craft as transaction will fail otherwise.
  • OutputsERC20 RecipeOutputERC20[]: Array of ERC20 recipe outputs. Developer must deposit() tokens onto the smart contract. This sets a hardcap on how much can be minted. Note that while multiple recipes can mint the same ERC20 the NFTCrafter’s balance sets an implicit global maximum. Developer can also
  • OuputsERC721 RecipeOutputERC721[]: Array of ERC721 recipe outputs.
  • balanceERC20: mapping(address ⇒ amount) : Track balance of output ERC20 tokens held by recipe. Updated whenever one of the write functions is called.
  • craftableAmount: uint256 Tracks the number of times this recipe can be used. A constraint invariant is that craftableAmount >= craftedAmount. Reject any transactions withdrawForRecipe or craftForRecipe, which would result in breaking the invariant. Can be updated by developer using depositForRecipe or withdrawForRecipe.
  • craftedAmount: uint256 Tracks the number of times this recipe was used. Useful for keeping track of the index of the ERC721 output id to distribute, and the withdrawable ERC20/ERC721.

Testing : Dummy Contracts

  • Create dummy ERC20/721 contracts for testing out our crafting mechanisms
  • Test locally with Ganache

NFT / Minter Architecture

The Owl NFT launcher will have the following architecture to enable dynamic NFTs

  • A standard mintable NFT contract
  • Plugin Minter contracts that are delegated mint rights and define how the id is generated
    • MinterSimple: Id as argument.
    • MinterAutoId: Auto incrementing id
    • MinterRandom: Random generated id
    • MinterBreeding: NFT collection inputs => derive new token id
  • Furthermore consider these additional parameters that can be combined to restrict minting in addition to id generation solutions. How should these be implemented?
    • IdRange: minId/maxId range that can be minted.
    • WhitelistSimple: Whitelist implemented as on-chain mapping.
    • WhitelistMerkle: Whitelist implemented as merkle root.
    • FeeERC20: ERC20 fee => Any id generation
    • FeeCrafting: Arbitrary ERC20/ERC721/ERC1155 inputs => AutoId or RandomId
    • CooldownSender: Cooldown on msg.sender (useful with Whitelist only as trivial to bypass otherwise)
    • CooldownBreeder: Cooldown on breeding inputs

Contract : NFTCrafter.sol (MVP)

Initial implementation to get NFTCrafter.sol into a working state.

Interface

Suggested interface for contract.

Read

  • getRecipe(recipeId)

Return recipe data. This includes inputs/outputs. And balances of these for this specific recipe.

Write

  • createRecipe(inputsERC20, inputsERC721, outputsERC20, outputsERC721)

Used by developer. Create new crafting recipe defining inputs/outputs.

  • depositForRecipe(recipeId, depositAmount, outputsERC721Ids uint256[outputsERC721.length][depositAmount])

Used by developer. Deposit outputs to smart contract to increase craftableAmount. amount parameter defines how many outputs to deposit. Only recipe owner can deposit assets.

For each ERC20 input defined in the recipe, we compute craftAmount*tokenAmountPerCraft and transfer accordingly. For each ERC721 input defined in the recipe, the developer has to pass a list of ids of length amount which are then transferred to the contract. These ids are then appended to the list of NFTs held by the recipe. Finally, the craftableAmount is incremented upon succesful transfer of all assets.

Sender address must have authorized transfer of tokens/NFTs by calling approve for each output asset, with an amount ≥ what will be transferred to the crafting contract.

  • withdrawForRecipe(recipeId, withdrawAmount)

Used by developer. Developer might withdraw up to the amount withdrawAmount <= craftableAmount - craftedAmount to reduce the craftableAmount. Only recipe owner can withdraw assets.

Withdraw outputs from smart contract. For ERC20 outputs, a simple transfer suffices of withdrawAmount*tokenAmountPerCraft. For ERC721, pop withdrawAmount from the input’s ids array.

  • craftForRecipe(recipeId, inputERC721Ids[inputsERC721.length])

Used by user. User can use this to make a craft operation.

Depending on the type of an input, it can be burned or unaffected. ERC20 inputs are transferred or just checked for balance against the required amount by the recipe. ERC721 inputs are transferred or checked for ownership. Input ids are also checked for validity if the validIds mapping is defined.

Sender address must have authorized transfer of tokens/NFTs by calling approve for each input asset, with an amount ≥ what will be transferred to the crafting contract.

`Crafter.sol` + `CrafterMint.sol` Refactor + Overhaul

Description

The Crafter.sol and CrafterMint.sol contracts would both benefit from significant simplification and refactoring. Rather than attempting to support multiple crafting recipes all in one contract, we're going to instead shift to one contract per recipe. Further, we're going to drop storing multiple inputs and outputs for ERC20/ERC721/ERC1155 and collapse all inputs into a generic RecipeInput type with the property enum TokenType.

Rather than dealing with splitting consumable types into multiple lists of unaffected, burned and locked inputs we can loop through the recipe ingredients once and deal with them on a case-by-case basis. This should dramatically improve our gas efficiency as well.

Implementation

  • Strip out specific inputs / outputs and replace with generic RecipeInput and RecipeOutput structs.
  • Strip out _recipies mapping and replace with recipe values set with initializer(...).
  • Strip out splitConsumeableERC...
  • Strip out batch transfers and balance assertions, replace with one loop iterating through ingredients.
    • The following if/else control structures should be added instead for balance assertion / transfers / lockups;
      • processInputERC20(...)
      • processInputERC721(...)
      • processInputERC1155(...)
  • Replace createRecipeWithDeposit : initializer(...) should have a parameter initialCraftable and initialIngredients which first creates the recipe and next calls depositForRecipe(...)
  • Update interfaces
  • Update test suite

Contract : Recipe Burn Address

burnAddress: Custom burn address that can be used to extend the contract’s functionality. Setting burnAddress to a developer controlled wallet would essentially make this a fixed price multi-asset sale contract.

NFT Specie Metadata `metadataToDNA`

Create the reverse function metadataToDNA(metadata, specieSpec): string which takes as input an individual NFT's metadata and encodes it as compact DNA.

`NFTMinter` Updates

Bugfix

Couple suggestions after my review of the NFTMinter contract.

  • security: speciesOwner modifier should NOT whitelist the address(this) and only check if msg.sender is owner
  • Index the events: owner field at least can be useful for UX integration
  • Remove name parameter as this is derived from https://docs.openzeppelin.com/contracts/4.x/api/token/erc721#IERC721Metadata-name-- no references to name (functions AND events) should be made in the smart contract. UI will simply pull this if it's present on the ERC721 contract.

Style

  • Function docs should use multi-line comments /** */ and not ///

Improvements

  • Assume Minter has the MINTER_ROLE (see grantRole link below)

  • Assume ERC721 token follows interface of ERC721PresentMinterPauserAutoId, with a simple mint(to) function (create a simplified interface, don't use the OpenZeppelin implementation as an interface)

  • As in the preset, tokenURI(tokenId) on the ERC721 returns _baseURI()/tokenId

  • We now want to create a new similar function on the Minter tokenDNA(tokenAddress, tokenId) returns the DNA (uint256)

  • Add a mintFeeToken, mintFeeAmount, mintFeeAddress parameters to specie

  • mint function, takes transfers ERC20 fee, calls mint(to), sets token DNA

https://docs.openzeppelin.com/contracts/4.x/api/access#AccessControl-grantRole-bytes32-address-
https://docs.openzeppelin.com/contracts/4.x/api/token/erc721#ERC721PresetMinterPauserAutoId-mint-address-

CrafterMint.sol

Overview

As of right now, Crafter.sol is implemented by holding output NFTs inside the contract, and distributing it to contract users from its own supply. This requires the game developer to allocate a supply for the output of the recipe.

The purpose of CrafterMint.sol is for output NFTs to be directly minted from the output NFT contract, skipping the requirement for game developers to pre-mint and removing potential lack-of-supply issues. Supply constraints can still be intentionally created, if that is desired by the game developer, but would no longer be logically necessitated. Additionally, this leads to gas savings on the game developer's side due to not having to pre-mint.

Implementation

The implementation will be very similar to the default Crafter.sol contract. The main difference is that there will be no depositing and withdrawing of output NFTs.

In addition to that, there will be an OUTPUT_NFT_ADDRESS state variable storing the contracts address of the NFT to be minted.

Finally, craftForRecipe(uint256 recipeId, uint256[] calldata inputERC721Ids) public will now mint the new NFT as opposed to transfer from its own balance.

Contract : ERC721 Input ID Validation

setValidERC721InputIds(uint256[] ids, boolean valid)

Used by developer. Set valid/invalid input ids for ERC721. Note that if the number of valid ids is less than craftableAmount then the practically craftable amount will be the less.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.