Contract Review: Desperate Apewives

Patrick Price
Northwest NFTs
Published in
10 min readNov 23, 2021

Bored, Desperate, and Unoriginal

Today we’re going to review the Desperate Ape Wives smart contract. We’ll cover how the authors burned $40k whitelisting their members, and we’ll take a look at an alternative solution that would have saved the DAW team all that cash.

Looking at the DAW website, I’m surprised to see this is actually a different team than the BAYC drop! Not sure what the IP laws surrounding this entails, I’m not a copyright lawyer. I would definitely be interested in the outcome of a copyright suit. These types of cases are defining in growing tech spaces, drawing the legal boundaries that will guide government response to new tools and their many uses. Some NFTs are adopting a standard license describing how the NFT may be used and how the associated art may be commercialized. The BAYC website does include a license, stating the owner of a BAYC NFT owns “the underlying Bored Ape, the Art” and may monetize it in whatever way they see fit. Does this expand to creating a sister club with its own questionably similar generative set? I leave questions such as these to the lawyers.

Instead we’re interested in the technical competency of the DAW contract. Does the contract do what it purports to do? We saw a handful of issues in the BAYC contract that potentially undermine the agreement put forth by its creators.

First let’s look at the public views of this contract:

This looks familiar. Looks like the provenance hash isn’t a constant, it’s not even set yet. This means, because the provenance hash was not set before sale, the DAW team could have reorganized the images after the mint but before reveal, giving themselves the best apes.

The DAW team has been nice enough to tell us ahead of time how many wives they reserved for themselves. We’ve mentioned Solidity styling before, these prepended underscores would typically mean these fields are private. That is not the case here.

This is interesting, a list of accounts. I guess the team can set administrators, whitelist addresses, and set some other limits.

Here are some sale times. I’m assuming the contract implements some kind of staged sale, with the whitelist and early supporters defined in “accounts” getting access to pre-sales or free mints.

Beyond the expected ERC-721 standard fields, there are just a few more unexpected functions: “payee,” “released,” “shares,” “totalReleased,” “totalShares.” These are reminiscent of the PaymentSplitter standard contract and I’m going to assume these functions have something to do with dividing up the enormous piles of ETH that tend to accrue in these types of contracts. Functionality such as these adds bulk to NFT contracts, and we recommend moving this functionality out to its own contract. Cheap clone payment splitters are easy to make, using paymentsplitter.io.

Now let’s look at the public writable functions.

This all looks straightforward. Buttons to enable or disable the sale. A couple mint functions and an airdrop function, standard for an NFT sale. “lockProvenance” suggests the aforementioned provenance hash can be frozen. This is great, exactly the kind of fix we would have liked for the BAYC contract. Ideally, though, this would have been set before the sale started, not after.

“Release” is another standard PaymentSplitter function, so these likely have to do with payout of the contract.

These are the last custom fields of the contract. Not just one, but two setters for early supporters! These names imply some redundancy, which is typically bad. Finally, there are setters for the provenance and sale stage times, and one more setter to whitelist addresses.

Nothing at this point is throwing any obvious red flags. There are signs of redundancy in some of the functions, not great but definitely not a showstopper. I do wonder how many early supporters there were. The setEarlySupporters function takes in a list of addresses, I’m guessing to update the accounts structure that we saw before. If there are more than a few early supporters, then logging them all on-chain could get very expensive.

Here’s our first look at the Desperate Ape Wives contract. The authors have decided to forgo the Solidity Style Guide’s recommended variable naming scheme, opting instead to follow no coherent naming scheme at all. This grab bag of _underscores, camelCase, and UPPERCASE is not organized in any meaningful way, and only adds confusion.

This is where that list of “accounts” is held. Interesting that the accounts have a “walletLimit,” but a “walletLimit” of 3 is set above in the contract. More redundancy?

This is the contract’s constructor, which registers a handful of administrators in the “accounts” data structure. This is a relatively short list, but it’s important to keep in mind that the data associated with these accounts is saved on-chain. Every “word” of data (256bits) costs 20000 gas to initialize, and each admin set above is using two words; one word to set the “nftsReserved” and one to set “admin” to true. False and 0 values remain uninitialized in memory and do not cost gas. 40000 gas at 100 gwei is .004 ETH, or around $20 to set each admin and doll out their reserves.

“Signs of redundancy” is an understatement. These three functions are doing the same thing, and could be reduced to one function. Additionally, these functions have the same cost as setting the administrators above, and if there were three different whitelists, how many addresses did the team need to include? More on that later.

Down into the business logic and we find the first of many mint functions. This one airdrops two wives to the “donation” account you might have noticed before, wife #1 and wife #2. This seems nice until you realize those two apes are supposed to be going to different charities, which means that the “donations” address must be controlled by a team member. This begs the question then, why even create this special function when you could just use the following adminMint function instead?

It’s also worth noting, because this function hard codes the ids of 1 and 2, this function will fail if those ids have been minted already. Not an issue, as long as you make sure to mint these out first.

This function could definitely be used to get those “donation” NFTs. As I mentioned above, if you ran this function before claimDonations, you wouldn’t be able to run claimDonations, because you will have already minted #1 & #2.

There’s more quality redundancy in this function; it’s checking whether the msg.sender is an admin both in the function modifiers, and in the function body. Whether an admin check is even necessary is debatable, admin status is implied because “nftReserved” is set for the msg.sender, and only admins are granted reserves in the constructor. That makes this function triply redundant!

Another important note, this function contains a loop that is bounded by the values set in “nftReserved.” The way the function is written, it will only mint every reserve wife for the given account. So if you’re the account with 90 reserved apes (see admin[7] in the constructor), you have to mint out all of those apes at once. We can find this specific 90 ape mint event in the transaction log for the contract, and see that almost 10 million gas was used at once. This is very large. The current target block size for Ethereum is 15 million gas, and the maximum is 30 million. The block size limit was only raised above 10 million around a year ago in 2020, and before then, any transaction of this size would have failed.

I like this function. It’s minimal but can still do everything “claimDonations” did, and more.

And finally, the main mint function. No obvious problems here, but it does seem like the contract writers were afraid of their users. There is a reentrancy guard in the function modifiers, which is unnecessary as the function is written in a secure way. There is also a check to make sure the minting account isn’t a smart contract. This is a good way to stop some exploits, but given the unnecessary reentrancy guard, I have to wonder: was this thoughtfully put in to prevent a specific kind of attack (for example, sniping rare tokens), or was it put in as part of a shotgun attempt to fend off unknown unknowns?

Plenty of redundancy here, and redundant functionality leads to higher deployment costs, but I have a feeling that the cost of pushing redundant bytecode up to the blockchain wasn’t the most painful the DAW team had to deal with. I’m curious how many addresses the DAW team chose to whitelist, because the solution they used does not scale well. Thanks to the public nature of the Ethereum blockchain, we can actually peruse the transaction logs for the DAW contract and find any transactions that were run to whitelist addresses. Luckily, these calls are some of the earliest transactions interacting with this contract and were easy to find.

These seven transactions represent every whitelisting transaction sent by the contract controller. Ignore the “0 Ether” indicator, instead note the smaller grey number. This is the gas fee for each transaction. We can see here, the DAW team spent a shocking 10 ETH whitelisting addresses before the sale. This is crazy but it does demonstrate the confidence the DAW team had in their community.

Unfortunately for the DAW team, this bonus 10 ETH out-of-pocket was totally unnecessary. Gasless whitelisting is possible using a Merkle distributor. With a Merkle distributor, the whitelist data is hosted off-chain and only a single value, the “root” of a Merkle tree containing our whitelist, needs to be loaded on-chain. To understand how this works requires a little background in the construction of Merkle trees. To construct a Merkle tree, first you start with a list of data, in this case, a list of addresses, and you apply a hash function to all those addresses. This new list of hashes are the leaf nodes of your Merkle tree, and you collect all these hashes together into the tree by pairing them off and hashing those paired hashes. You keep pairing off and hashing until you’re left with a single final hash value, and that is the “root” of your Merkle tree.

What’s great about this structure is it provides a smart contract an easy and cheap way to check whether any given address is on the whitelist. To prove that they are on the whitelist, the member needs a “proof” that they can present to the contract. Suppose the above image is our Merkle tree whitelist, in order to prove to the contract that they are on the list, the owner of Address A needs the values in Hash B and Hash F. They can present that information to the contract, which will hash their address, climb it’s way up the tree with the hashes provided, and then verify that the root hashes match. This method requires distributing the proofs to whitelist members, which, with good web design, can be performed directly at sale time. This structure confers the added benefit that it does not expose whitelist members addresses before they have made a purchase.

Thanks to an abundance of caution by the DAW team, this contract is very secure. It’s surprising that they haven’t yet loaded a provenance, given the sale occurred almost a month ago. Guess the DAW team just doesn’t care, and I get the impression consumers don’t really care either. Until we educate the community and create systems of enforcement, the NFT business is going to remain a wild west that, to the intelligent consumer, comes off as an expensive way to download jpegs. I’m glad to see communities like HonestNFTs picking up the charge, working to educate consumers and creating tools to detect abuse by NFT projects.

While security isn’t an issue with this contract, there are some clear inefficiencies that ended up being very expensive for the DAW team. $40,000 might seem like a drop in the bucket when you just raked in $3.5 million, but upfront costs like these can be prohibitive for smaller creators, and regardless, that’s still one less Benz in your new car collection.

Are you working on an NFT project? Need smart contract or web development support? Contact us at NorthwestNFTs.com

--

--