Skip to content

Customizing Contracts - Allow Public Mint #8

@AddressXception

Description

@AddressXception

As a user i would like to purchase up to 5 tokens per transaction so that I can save time and costs minting multiple tokens.

It is common to set a limit on the number of tokens that can be minted in one transaction. Realistically, it is actually very easy to bypass this limit if you know what you are doing, but it exists to provide a good user experience for people who are not technical. There are a lot of things that can go wrong when trying to call a contract (even from a well-designed web frontend) and by limiting the amount of tokens that a normal user can mint, we are also limiting the amount of money they are risking when they call our functions.

Perhaps an example is useful. Remember how we said that each operation on ETH (e.g. adding two numbers together) has a fixed cost in gas, but the price of gas changes? Well, there's something else to consider. When a user submits a transaction they also have to submit a gasLimit with that transaction which is included specifically to protect against recursive function calls draining someone's wallet. Metamask and other tools will usually handle estimating the amount of gas, but its not perfect and it can lead to "out of gas" errors.

Imagine if a user tries to mint 100 or 500 tokens, and the call reverts, it is possible they lose everything. We want to protect against that.

To implement the mint function, we need a new public function that accepts a quantity. We also need a member state variable that defines what the limit is (in our case, 5). You can make this value constant since it won't change.

For the mint function itself, we need to validate our inputs. Logically, we need to check that the caller is requesting a quantity less than or equal to our quantity call limit. We also need to check that the caller submitted enough ETH as part of the transaction for payment. We also need to check that the overall limit of 1000 tokens has not been breached.

In order to do this, we use ethereum's require function. Require is just a shorthand function that says "check this condition else revert". you can think of it like an assert or an expect in other languages, but with some very specific behavior.

Ethereum actually has several reversion operations that behave differently. You can read about them in this Consensys Github.

Since we are validating inputs we want to use require since we want the remaining gas to be returned to the user. We always put these at the beginning of the function so that we minimize the impact to the caller if the function does actually revert.

Once we've checked our inputs for validity we can execute the mint. Similar to the private mint we executed when we deployed the contract, we need to iterate over the range of the requested quantity and call _safeMint. We can use the totalSupply() function call in the ERC721 standard to determine what the next token index is to mint.

We also need to make sure we are minting to the function caller and not to an arbitrary address like we did initially. There are built-in functions that can help us here. We can use msg.sender to derive who the caller is. We could also use tx.origin which defines the origin of the transaction, but this has some undesirable side effects. State in Solidity, like javascript, can have different scope depending on the calling context. Here's a great blog article describing the difference between tx.origin and msg.sender.

Getting this right can sometimes be difficult. Fortunately, OZ includes a variety of different utilities that can help us ensure we are using the right functions.

The Address functions are incredibly useful

Once our mint function is in place, it's time to write some unit tests to validate our assumptions.

What happens when you call the mint function with 0 quantity?

What happens when you call the mint function with 0 ETH?

What happens when you call the mint function with 6 quantity?

How does the mint function behave when the mint is nearly sold out and a user calls the contract to mint 5 but there are only 3 left?

It is important to make sure we test all of the edge cases we can think of so that we can have a high degree of confidence there are no deficiencies in our logic. Our first goal is to reduce failed transactions for users, but we also need to make sure our code cannot be exploited by bad actors.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions