ℹ️ This article assumes a basic/intermediate understanding of game theory, blockchain transaction settlement, entropy (mathematics) and Solidity. However the conclusions and reasoning are usable to anybody evaluating risks with different NFT drop approaches.
NFTs that are released for sale typically drop as a set, and each asset in the set has a different value or may appeal differently to different people. Even with 500 still frames of Joe Biden eating a sandwich, some are great and some are terrible.
This article reviews different approaches to randomization I have seen in the wild or heard about.
Of course product designers will ask for the holy grail, but… Fair, random and immediate sales are not possible on decentralized ledgers. ("No FRID".) Many creative solutions have been tried to trade between these features.
Below are summaries and examples of randomization strategies. Please note that the summary describes a best possible implementation of the randomization strategy and does not represent a William Entriken audit of each mentioned contract.
Base concepts
It’s dangerous to go alone, take these concepts. 🗡
Naive describes a party using the most popular software and following all the directions properly.
Malicious describes a party that is doing something other than the most naive thing and which would give others a worse outcome than if they did the most naive thing.
As of 2022-02-07, 85% of miners on Ethereum Mainnet participate in MEV.
Miner Extractable Value (MEV) is where people that process your blockchain transactions will front-run, censor or sandwich your transaction to profit at your expense.
Advanced MEV are techniques that can be implemented with MEV but are not currently seen in the wild. This includes controlling the BLOCKHASH
. I am developing these techniques and they will be more popular starting in 2023.
Pricing the assets (uniform price, individual prices, blind auctions, time-based auctions, pooled auctions, bonding curves, quantity discounts, friend discounts and other price discovery) is an important consideration. Pricing system design is not part of this article and systems/bidding can be NP-hard.
No randomization
graph LR
subgraph Numbered sales
m[Metadata is public]-->p
p[Pick one]-->b[Buy that]
b[Buy that]-->g[Got that]
end
Here all the NFTs are available for sale and buyers can come get whichever ones they want. Buyers know exactly what they are getting (e.g. metadata is public).
Fair | Random | Immediate |
---|---|---|
✅ Yes | ❌ Not at all | ✅ Yes |
Each purchasing transaction is a signal expressing value for a specific asset over all other available assets (possibly even with next-for-grabs). That signal could be front-runned using MEV. And these MEV participants could even outbid each other. The threat is so abstract that I am still rating this as “fair”. Also, this could be addressed by using different princing mechanisms.
Examples:
Some people dispute whether Terra Nullius is an NFT.
- Su Squares (smart contract) is a numbered sale where the location (x-y grid) is public ahead of time and graphics metadata is added by token holders.
- CryptoPunks (smart contract) is a numbered sale where the images were published before the contract went live.
Next-for-grabs
graph LR
subgraph Next for grabs
mn[Metadata is public]-->bn
bn[Buy next available]-->gn[Got that]
end
Here all the NFTs metadata is public. Each buyer has only the choice of buying the next available one. Because multiple parties can participate on the blockchain simultaneously, it is possible that a buyer things they are getting one thing but when the transaction settles they got a different thing.
Fair | Random | Immediate |
---|---|---|
✅ Yesish† | ❌ Not at all | ✅ Yesish‡ |
† If somebody is purchasing something, an MEV participant could jump in line to get that one.
‡ It is possible that the next available item will be objectively undesirable. In this situation, the Nash equilibrium is that nobody buys your product—they are all waiting for someone else to take one for the team.
Both of these can be addressed by using a pricing mechanisms, such as by using a time-based auction.
If you would like to mint the NFTs not in ascending order, consider using y=mx + b (mod supply)
(with m
coprime to 1
through your total supply). This is a bijective function that will map your supply to a different order.
Examples:
Some people dispute whether Terra Nullius is an NFT.
- Terra Nullius (smart contract) is a next-for-grabs giveaway.
Immediate reveal
graph LR
b[Buy one]-->g[Got one and found out which one it was]
A customer places an order and receives one of the remaining available tokens, selected immediately using BLOCKHASH
from the previously-settled block.
Fair | Random | Immediate |
---|---|---|
❌ No | ❌ Noish | ✅ Yes |
When a naive person submits a transaction, they do not know which block it will settle after. So for them the outcome is unexpected. But in 2022 with widely-available MEV, you must assume somebody using your contract can know (or maybe even set) the BLOCKHASH
from the previously settled block.
Because some people have this advantage and the naive person will not, the immediate reveal is not fair. And because those people with the advantage can choose (or influence the choice of) their tokens, this is not so random.
You can reduce/minimize the advantage using the Entriken-Fisher-Yates multiplayer shuffling algorithm:
Unbiasing the input entropy can be optimized out for a BLOCKHASH
/shuffling application. But unbiasing the output is necessary if the inventory of NFTs has patterns (e.g. the first ones are better or have lower tokenID
s).
This algorithm can be made more granular by inputting perplexity rather than specifying entropy in bits. Call me if you found an application where that matters.
// Cryptographic primitives ////////////////////////////////////////////////////
/// @notice Normalize an entropic value over the full word size
/// @param input a source of entropy
/// @return normalized value across the range [0, 2^256)
function _unbiasEntropy(uint256 input) internal returns (uint256) {
return uint256(keccak256(abi.encode(input)));
}
/// @notice Mixes new entropy into `_entropyState`
/// @param additionalEntropy a source of entropy to use
/// @param howManyBitsToMix how much influence the `additionalEntropy` has
function _mixEntropy(uint256 additionalEntropy, uint8 howManyBitsToMix) internal {
uint256 clippedAdditionalEntropy = _unbiasEntropy(additionalEntropy) % (1 << howManyBitsToMix);
_entropyState = _unbiasEntropy(_entropyState ^ clippedAdditionalEntropy);
}
// Shuffling ///////////////////////////////////////////////////////////////////
uint256 private _entropyState = uint256(blockhash(block.number - 1));
CustomArrayType private _inventory;
function _takeFromInventory() internal returns (uint256 retval) {
require(_inventory.length > 0, "Inventory underflow");
return _inventory.popAtIndex(_entropyState % _inventory.length);
}
Here is the tradeoff:
Participant | Participant contributes zero entropy | Sweet spot? (N bits) | Participant contributes 256 bits of entropy |
---|---|---|---|
Naive buyer | Same as next-for-grabs | Random, gets one of 2^N items | Random, gets ANY item |
MEV buyer | Same as next-for-grabs | Can pick one of 2^N items | Can pick ANY item |
You can see there is a basic axiom here. The amount of randomness in an immediate random sale is directly equal to how much advantage an MEV buyer has over a naive buyer.
So how does a naive buyer upgrade to a MEV buyer? A buyer can rely on rerolls (available today) or hash management (advanced MEV). A reroll means that if a transaction was about to be settled and the transaction would result in an unfavorable token selection, then that transaction is bounced and it is not settled. Under proof-of-work, the cost of selecting a desirable blockhash increases exponentially with the proportion of undesirable BLOCKHASH
you wish to exclude. Under proof-of-stake the cost of hacking BLOCKHASH
is still exponential but the coefficient is about 1000× lower.
Examples:
- Meebits drop was hacked for millions because it allowed buyers to know which ID they were getting while also accidentally revealing which IDs were valuable. The attacker used the “rerolls” technique.
- The Photon Project (smart contract) uses a simple immediate reveal (
_getRandomNumber
) that any MEV participant could game. - LazyArray data structure allows to efficiently implement sample without replacement.
Iterative commit-reveal
graph LR
b[Buy one]-->w[Wait]
w[Wait]-->g[Got one and found out which one it was]
A commit-reveal mechanism is used so that anybody can purchase a token, but they won’t know what they got until later when it is revealed.
Fair | Random | Immediate |
---|---|---|
🤷♀️ Maybe in 2022 | 🤷♀️ Maybe in 2022 | ❌ No |
Reveals can happen with agency from that buyer, from a subsequent buyer, and/or from an administrator of the contract.
And the timing of the reveal can be “soon” (influenceable by that buyer), “at end” (when the entire drop is complete) or “at checkpoint” which is some point before the entire drop is complete.
There are several ways to get this entropy:
- A specific future
BLOCKHASH
or other source of entropy on-chain - An offchain oracle
I’ll open a separate discussion on offchain oracles later.
Let’s clarify about an offchain oracle, first that is not a decentralized system and second it is not random. Many people have a thought otherwise, but do not have a strong conviction/evidence.
There are ways for an MEV participant to manipulate the BLOCKHASH
. But these techniques are not well-known in 2022. I’m working to change that. Hacking NFT drops will get a lot easier in 2023 due to proof-of-stake.
Examples:
- Area (smart contract) uses a “commit train” so that each buyer is doing a commit as well as the reveal for the previous buyer.
- Non-Fungible Fungi (smart contract) uses an VRF oracle to pick token indicies.
Metadata reveal at end
graph LR
b[Buy one]-->w[Wait]
b2[Buy one]-->w[Wait]
b3[Buy one]-->w[Wait]
bn[...]-->w[Wait]
w[Wait]-->g[Owner reveals after done]
In this scenario, nobody knows which items they got until the drop is complete and then the owner reveals to everybody.
Fair | Random | Immediate |
---|---|---|
❌ No | ❓ You decide | ❌ No |
There are a couple scenarios where this can go horribly wrong.
- The owner could be hit by a bus or rug pull, then nobody receives their assets that they purchased.
- The owner could have a choice of which metadata to reveal, giving the best ones to their friends (this can be fixed by committing the metadata before sale).
- The owner could tell their friends which metadata are good and which are bad, their friends get the good ones, everyone else gets what’s left.
- The metadata could be accidentally released, so people that find it are like “friends” above.
There are a few mitigations to make this not so terrible.
- The owner could use staking to disincentivise themselves from rug pulling or getting hit by a bus.
- The reveal could be set for a specific time and the randomization could be picked in a way that uses entropy from several
BLOCKHASH
s. If done correctly, this limits the impact of manipulating anyBLOCKHASH
. - Post the list of possible metadata upfront. If you know it and you are hiding it and you can buy anonymously this is a huge red flag.
Examples:
- Meebits accidentally revealed which metadata was good by publishing the data on IPFS and making it available on their website
- Peter Kacherginsky has a good analysis of the hack
Reading circle questions
- What is the difference in risk profile from a founder that publishes the metadata before a drop starts versus after it ends?
- Will it be random if the founder promises to use next week’s Dow Jones Industrial Average opening price as a seed for random distribution?
- Is the risk of rug pulls/disappearance moot when you buy in a presale?
- How do auctions and other price-discovery mechanisms offset the “abstract” risk discussed under “no randomization” above?
- Is there any benefit to using an auction for drops which are designed to be “random”?
▧
Comments
The official X thread
@fulldecent
Please discuss this topic anywhere and let me know any great comments or media coverage I should link here.