Tag Archives: proxy contract

Dead mfers Contract Review

Dead mfers is a collection by sartoodles, who is an active member of the unofficial mfers community, and has created a few mfers derivative collections. Owners of Dead mfers receive free airdrops regularly on the polygon network, and more benefits will be unlocked when the collection is 90% minted. There is also a video game in the works. Let’s look at the contract code

Mint Random

The mint function used for Dead mfers is mintRandom:

    function mintRandom() payable public
    {
        require(mintRandomEnabled == true, 'Random minting is not enabled for this contract');
        require(msg.value == mintFee, 'Eth sent does not match the mint fee');
        
        _splitPayment();
        
        uint256 tokenID = _drawRandomIndex();
        
        _safeMint(msg.sender, tokenID);
    }

This requires that random minting is enabled, and the fee passed in is correct, in this case 0.0069 ETH. _safeMint is a standard ERC721 function from OpenZeppelin, so let’s look at the middle 2 functions: _splitPayment and _drawRandomIndex.

Split Payment

    function _splitPayment() internal
    {
        if(msg.value != 0){
            uint256 splitValue = msg.value / 10;
            uint256 remainingValue = msg.value - splitValue;
            
            payable(shareAddress).transfer(splitValue);
            payable(owner()).transfer(remainingValue);
        }
    }

This function sends 10% of the mint fee to shareAddress, and the remaining 90% to the contract owner. The shareAddress is set when this contract is initialized by the factory contract, which is covered in more detail below. If you look at any of the Mint Random transactions, you can see that 10% of every mint fee is sent to 0xe28564784a0f57554d8beec807e8609b40a97241, aka autominter.eth. This is how AutoMinter makes money by providing tools to NFT creators.

Sample transaction showing 10% of mint fee going to autominter.eth

Draw Random Index

    function _drawRandomIndex() internal returns (uint256 index) {
        //RNG
        uint256 i = uint(keccak256(abi.encodePacked(block.timestamp))) % remaining;

        // if there's a cache at cache[i] then use it
        // otherwise use i itself
        index = cache[i] == 0 ? i : cache[i];

        // grab a number from the tail
        cache[i] = cache[remaining - 1] == 0 ? remaining - 1 : cache[remaining - 1];
        
        // store the position of moved token in cache to be looked up (add 1 to avoid 0, remove when recovering)
        cachePosition[cache[i]] = i + 1;
        
        remaining = remaining - 1;
    }

This function gets a pseudo random number between 0 and remaining, then returns that number (index). The rest is mostly to coordinate between other minting functions like _drawIndex that are not used for Dead mfers, so that the same token isn’t minted twice.

That’s about it – the code is relatively minimal without much going on. One downside to this contract is that you can only mint a single token at a time. I suspect that if it allowed batch minting of multiple tokens in a single transaction, like in Backgroundmfers or 3DMutantMfers, then a lot more Dead mfers would be minted.

I could conclude the review here, but there’s some interesting additional details to cover. If you’re looking at the code on etherscan, you may notice it says Minimal Proxy Contract for 0x72668b08926a69ae9c926aeb572559efc7f42cd6. What does this mean?

AutoMinter Proxy

The Dead mfers contract is not a custom contract. Instead, it is a minimal proxy for a contract by AutoMinter. This means that the Dead mfers contract forwards all the contract interactions to the autominter contract code, while the state data that records your actions and NFT ownership is maintained within the Dead mfers contract. By using this minimal proxy pattern, sartoodles was able to re-use someone else’s code, and save ETH with a cheaper contract deployment.

When deploying a minimal proxy contract, the main thing is to provide initialization parameters. This is what customizes the contract for your use vs someone else’s. In other words, many other contracts could be minimal proxies for the same autominter contract, but each minimal proxy has been initialized differently. For Dead mfers, here are the first 5 initialization parameters:

To find these, start on the contract page. There is a More Info section in the upper right that looks like this:

In the middle row, it shows “… at txn” and then a transaction hash. Click on that transaction hash to see the transaction details page, which looks like this:

You can see when the transaction happened, the Contract that was interacted with (the AutoMinterFactory covered below), the transaction Value of 0.025 ETH, and lots of other details. At the bottom, click on the Click to see More link. That displays the Input Data, which shows that a create function was called.

What you see is the binary data passed in to the create function. At the bottom, click Decode Input Data to see a human readable table of parameters, such as the name_ being Dead mfers. This create function actually belongs to a different contract, AutoMinterFactory. Below is a short video showing the contract navigation to find all this information, as well as the AutoMinterFactory contract described next.

https://streamhacker.com/wp-content/uploads/2022/04/Sartoodles-Dead-mfers-contract-navigation-10-April-2022.mp4

Auto Minter Factory

The AutoMinterFactory contract does the actual deployment and initialization of the minimal proxy contract used by Dead mfers. As part of that initialization, it sets some of its own parameters on the contract, such as the shareAddress mentioned above.

The create function on the factory contract is relatively clear: (note that the actual create function used by the latest AutoMinterFactory might be different than below, because it’s an upgradeable contract behind a proxy contract):

    function create(string memory name_,
        string memory symbol_,
        string memory baseURI_,
        string memory appId_,
        uint256 mintFee_,
        uint256 size_,
        bool mintSelectionEnabled_,
        bool mintRandomEnabled_,
        address whiteListSignerAddress_,
        uint256 mintLimit_,
        uint256 royaltyBasis_,
        string memory placeholderImage_) payable public
    {
        require(msg.value >= fee, 'Must pass the correct fee to the contract');
        
        address payable clone = payable(ClonesUpgradeable.clone(erc721Implementation));

        AutoMinterERC721(clone).initialize(name_,
            symbol_,
            baseURI_,
            msg.sender,
            mintFee_,
            size_,
            mintSelectionEnabled_,
            mintRandomEnabled_,
            whiteListSignerAddress_,
            mintLimit_,
            royaltyBasis_,
            placeholderImage_
        );
        
        emit ContractDeployed(appId_, appId_, clone, msg.sender);
    }

The erc721Implementation address points to the current deployed version of AutoMinterERC721, so create clones this contract then initializes it.

To recap, the contract deployment worked like this:

  1. The create function is called on a proxy contract that forwards to the AutoMinterFactory implementation contract (a proxy is used so that the factory contract can be upgraded in the future)
  2. The AutoMinterFactory implementation contract clones and initializes a minimal proxy contract for the current version of the AutoMinterERC721 implementation contract
  3. The minimal proxy contract is initialized with the parameters from the create function in step 1
  4. All calls to the minimal proxy contract (for Dead mfers) are forwarded to the AutoMinterERC721 implementation contract

None of the above is bad or dangerous; it’s necessary complexity to provide re-usable & customizable contract code. The result is a simple & cheap contract.

Upgradeable

You may also notice that one of the first lines of code says the contract is upgradeable:

contract AutoMinterERC721 is Initializable, ERC721Upgradeable, OwnableUpgradeable

If the Dead mfers contract was a regular proxy contract, then this line above would make it possible to deploy a new version of the AutoMinterERC721 contract. Upgradeable NFT contracts are not necessarily bad, but it is a warning sign, because the implementation contract could be changed at any time, potentially introducing insecure code. In the case of Dead mfers, because it’s only a minimal proxy, the implementation contract cannot be upgraded.

Slither Analysis

Slither is a python tool for static analysis of Solidity contracts. You can use it to get a quick summary of the contract code, and then look for any deeper issues.

$ slither 0x50092539a224953d82995c9d950e042da4556283 --print human-summary

In this case, some of library contracts are marked as Complex, while the main contract AutoMinterERC721 is not, which is good. But Slither also says there are 3 high severity issues. Let’s see what those are:

$ slither 0x50092539a224953d82995c9d950e042da4556283
AutoMinterERC721._drawRandomIndex() (contracts/AutoMinterERC721.sol#114-129) uses a weak PRNG: "i = uint256(keccak256(bytes)(abi.encodePacked(block.timestamp))) % remaining (contracts/AutoMinterERC721.sol#116)"

OwnableUpgradeable.__gap (@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol#82) shadows:
	- ContextUpgradeable.__gap (@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol#31)
ERC721Upgradeable.__gap (@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol#431) shadows:
	- ERC165Upgradeable.__gap (@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol#36)
	- ContextUpgradeable.__gap (@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol#31)

The first issue says that _drawRandomIndex, called by the mintRandom function, uses a weak PRNG, which stands for pseudorandom number generator. The random index for a token is generated by doing a modulo on the current block timestamp, which apparently could be manipulated by an ethereum blockchain miner, to reorder the block to get a different random index. This may be a problem in other situations, but in this case, it doesn’t really matter which random token you get when you call mintRandom.

The second and third issues are about state variable shadowing in some of the OpenZeppelin upgradeable contract dependencies. This means the __gap variable is used in multiple contracts but is not explicitly assigned. This appears to be a design choice in upgradeable contracts, to explicitly leave room for new state variables in future versions of a contract. So this high severity issue isn’t actually a problem here either.

All the other issues Slither detects either don’t really matter or don’t apply to how the Dead mfers contract is used.

Conclusion

Overall this is a simple contract. It is unfortunately limited to minting one at a time, and it doesn’t look like AutoMinter supports batch minting. This is one reason why many other projects will deploy their own custom contracts, in addition to custom code for different minting options. So if you’re ok with single minting, AutoMinter might be a great option for creating your own NFT collection. And be sure to mint your own Dead mfer to receive some free airdrops, or if you want to play the upcoming video game, recently teased on @Sartoodles.