How Stoner Cats Lost Collectors $800K in 35 Minutes

9 min read Original article ↗

Who’s actually to blame and what the hell just happened?

Ilan Gitter

Press enter or click to view image in full size

Stoner Cats #10013

After Stoner Cats’ launch on July 27th many people were asking exactly that. Was the team responsible for a shoddy launch? Does MetaMask have a bug with sped up transactions? Is there an issue with the Stoner Cats’ smart contract logic?

TLDR; Yes and no but mostly 🤷.

Users by and large lost funds via “out of gas” transaction errors. The Stoner Cats front-end code base doesn’t specifically set a gas limit for transactions and instead uses ethers.js to submit the transaction to the connected Web3 Wallet. If a gas limit is not specified, ethers.js estimates one and doesn’t add a buffer, thus leaving many users with a gas limit just under the required amount for their transaction to succeed. If all this leaves your head spinning don’t worry; we’ll walk through all of it in detail soon.

So maybe ethers.js is ultimately at fault here, but is it their responsibility to add a buffer? Should MetaMask enforce the fact that transactions have reasonable values? Could there have been anything the Stoner Cats team could have done to prevent this? We will dig further into what this all means and why a few lines of code resulted in about $800K of lost funds…

Press enter or click to view image in full size

Stoner Cats #163

The Launch

On July 27th at 2PM PDT, average gas prices on Ethereum sky rocketed from about 30 gwei to over 600 gwei in just a few seconds. For a few thousand people, panic ensued and they frighteningly pushed “Speed Up” in MetaMask repeatedly, praying to the great Vitalik that he may bless their ownership of a stoner cat. Within 35 minutes all 10,420 NFTs were sold out. Over $8,000,000 was raised for an innovative animated series backed by truly great actors and animators, but as the dust settled it became quickly clear that something was amiss. People somehow lost almost $800,000 dollars worth of ETH due to failed transactions during the drop.

Press enter or click to view image in full size

Stoner Cats #2674

The Stoner Cat Discord channel was flooded with people claiming they lost hundreds and even thousands of dollars due to a bug in the Stoner Cat contract. They incessantly berated the mods and the team for a shoddy launch for hours. Some blamed MetaMask, some blamed the gas prices, and others blamed the ones trying to buy the little stoners in the first place. They are all wrong and in some ways they’re all right. This fight is still going on now and honestly I can’t entirely fault anyone.

So what is a Gas Limit?

Before we explore the lifecycle of a Stoner Cat NFT “Mewnt” let’s first back up and answer the question that many of you might be wondering:

What the hell is gas and why is there a limit?

In a way you can think of executing a successful transaction on Ethereum like filling up a car’s gas tank at a capitalist gas pump. At this very entrepreneurial gas station you get to choose how much you’re willing to pay per gallon which determines your place in line. Pay more and you’ll be able to fill up the tank quicker. This is the “gas price” on Ethereum. It’s how much you’re willing to pay per gallon.

The gas station recommends a maximum number of gallons you want to receive. This is the “gas limit”. If the tank happens to be small, you won’t hit this limit and the tank will just fill up and off you go. You only pay for what you use. If, however, you happen to be filling up a gas tank with a hole in the bottom, the gas limit helps cap the maximum amount of money you’ll pay. Without this gas limit, you’d end up spending every penny you have. Now here’s where it gets interesting. In Ethereum, a transaction fails if you are unable to fill up the proverbial tank. Choose too few gallons and you end up SOL with an empty tank and a fat bill. Bye bye $500. No Stoner Cat for you.

How do you even know what the right Gas Limit is?

Thankfully, the gas station can simulate filling up your tank without you ever even having to step foot into that sadistic place and come up with an estimate of how many gallons you’ll need. But here’s the key. It’s an estimate. There’s no guarantee that it’s the exact amount you’ll need. It’s like a person who’s really good at guessing the number of peas in a big ol’ pickle jar. Sometimes he’s over, sometimes he’s under.

Press enter or click to view image in full size

Hammy says 3687 peas

Stoner Cats’ “Mewnt” Transaction Flow

Warning: We’re about to dive into code so feel free to skip to the next section for the juicy conclusions.

Here’s how a mint worked if you tried to mint through their website.

Step One:
You choose how many cats you want and click a button to sign a transaction. The code here curiously comments out the GasLimit (maybe they had issues with it before?) and doesn’t set one at all when submitting the mint transaction.

// const gasLimit = 300000;
const res = await collectibleData.contract.functions.mewnt(quantity, {
from: account!,
// gasLimit,
gasPrice,
value: priceBN.mul(BigNumber.from(quantity)),
});

What could the Stoner Cats team have done here to fix this?

This code could have estimated the gas required for this contract call and added a buffer to be sure that it would go through properly. In fact, it’s only a few more lines of code 😳

// add 20% buffer
const addGasLimitBuffer = (value) =>
value.mul(BigNumber.from(10000 + 2000)).div(BigNumber.from(10000))
const gasLimit = await collectibleData.contract.estimateGas.mewnt(quantity)
const res = await collectibleData.contract.functions.mewnt(quantity, {
from: account!,
gasLimit: addGasLimitBuffer(gasLimit),
gasPrice,
value: priceBN.mul(BigNumber.from(quantity)),
});

Step Two:
Now it’s out of Stoner Cats’ hands. This collectibleData will go ahead and interact with the Web3 Provider (likely MetaMask) and handle submitting the transaction to the blockchain after it’s signed. Stoner Cats utilizes a utility library called “ethers.js” to help with this, and admittedly this library is actually quite good. In this case however, it will eventually trigger this code:

if (tx.gasLimit == null) {
tx.gasLimit = this.estimateGas(tx).catch((error) => {
if (forwardErrors.indexOf(error.code) >= 0) {
throw error;
}
return logger.throwError(...error message...);
});
}

That’s it. The root cause. This little chunk of code was the final defense and lost people almost $800K. No buffer was added to the estimate and that pretty good pea counter f*cked up a lot of people’s days.

Press enter or click to view image in full size

Stoner Cats #1152

Other Potential Factors

Smart Contract Optimization

I heard one claim that because the Stoner Cats smart contract was optimized with 15,000 runs while compiled, it is more difficult to estimate the correct amount of gas required to execute a transaction. I was unable to find any references to this behavior online but would love to see some links in the comments if anyone knows more. It makes sort of intuitive sense but I have yet to dive into the solc compiler code.

MetaMask

Many people jumped to blaming MetaMask and to be honest I was suspicious as well. There is a case to be made that MetaMask should check transactions for sane values before submitting them to the blockchain. In reality, MetaMask is often the last line of defense between us and Aping into terrible decisions with hastily written potentially poorly audited code (note: Not a dig at Stoner Cats at all. A surprisingly large percentage of blockchain projects can be described this way). If we are going to bring Ethereum to the masses we need to take extra precautions to make sure peoples’ experiences are safe and seamless. It’s difficult enough to try to explain what an NFT is, yet alone why all these other nuances could cause you to lose all your money.

The People

Lastly some will say that people should have a better understanding of what they’re doing and should have manually checked the gas limit before submitting their transaction. While I philosophically somewhat agree here, I think this one is a bit tougher to justify. Many people are used to the safety baked into financial products they use. Fraud detection has improved immensely over the years and you can usually get your money back on unauthorized credit card charges. We are also used to the process of seeing our email online, which in reality is an insanely complicated feat, by simply typing “gmail.com” and clicking enter. I hope using Ethereum will one day be at this same level of abstraction, but until then I do think we have to try to educate ourselves and others about the dragons hidden in the mempool.

Press enter or click to view image in full size

Stoner Cats #3852

Conclusion

In the end, the blame could be placed on just about any one of the above parties for the failed attempts at mewnting some blazed kitties.

  • Stoner Cats’ code should have included a specific buffered gasLimit. This is a common practice and is done by both Uniswap and my own personal NFT project.
  • Ethers.js should add a buffer anywhere it estimates gas to be used as a fallback (I will likely open a PR in the near future).
  • MetaMask could check transactions that flow through it to ensure that values are set safely.
  • And finally, even people just trying to get their hands on a Vitalik Catsington could have manually set a higher gas limit via something like MetaMask’s advanced transaction editor.

If you ask me, this last one is absurd and we should move away from placing blame on the end user. It’s like saying we should be double checking the weight of a potato chip bag before buying it. Just give me my Zapp’s Voodoo chips and let me finish this nature documentary.

Press enter or click to view image in full size

Stoner Cats #5992