0

My smart-contract's size has suddenly ballooned to 24.385 KB - and I have no idea why it's happening.

The contract is a pretty standard ERC721 Contract: it imports OpenZeppelin's ERC721.sol contract, as well as several OpenZeppelin libraries, plus another contract I wrote - so there's nothing out of the ordinary about this. It's all really standard stuff that I've seen done in a million other projects.

Here's a breakdown of all the ".sol" file in my project:

enter image description here

NOTE: I'm using HARDHAT and ETHERS.JS for my development.

As you can see, it's that last contract on the list, PHPMinter, that's crazy big, clocking in at 24.385 KB
(By the way, "PHP" has nothing to do with the PHP programming language, it's just an acronym for stuff in my project.)

So here's the thing: that PHPMinter contract is really NOT that big in terms of how much code is in it, how many Functions it's got, or what it's doing. As a matter of fact, I actually took a bunch of code OUT of it, and pasted it into a new separate contract called "PHPCollectionManager" - which I created explicitly for the purposes of making PHPMinter smaller.

So now PHPMinter imports PHPCollectionManager into itself, and everything works perfectly fine - except that PHPMinter is still totally maxed-out in terms of space. So basically as soon as I add just one more line of code into it - the whole thing "crashes," and I get this error in Terminal: ProviderError: Error: Transaction reverted: trying to deploy a contract whose code is too large

I'd really like to know what in the world is happening here.

I'm pasting the code below - please note how:

  1. It's really not that big, and
  2. I even completely truncated all my "require" error messages throughout so they're minimal and not at all verbose.

THE CODE:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;


import "hardhat/console.sol";

import "./PHPCollectionsMngr.sol";

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";



contract PHPMinter is PHPCollectionManager, ERC721, ERC721Enumerable, ERC721URIStorage, Pausable {
   using Counters for Counters.Counter;
   using Strings for uint256;


   // Mapping to keep track of Addresses that have already MINTED:
   mapping(address => uint256) private alreadyMintedGMPHolders;   
   mapping(address => uint256) private alreadyMintedSMPHolders;  


   // Base Metadata URI:
   string private baseTokenURI;


   // EVENTS:
   event BaseURIUpdated(string newlyUpdatedURI);
   event GMPClaimed(address indexed GPBuyerAddress, uint256 indexed passIDNumber);
   event SMPBought(address indexed SPBuyerAddress, uint256 indexed passIDNumber);

    

   constructor() PHPCollectionManager() ERC721("PHPMintNFT", "PHP")   
      baseTokenURI = "https://mydomainname.com/MP/jsonFILES/MPMetadata";

      console.log(">baseTokenURI = %s", baseTokenURI);
      console.log(">GMPCounter = %s", GMPCounter);
      console.log("\n\n>'SMPCounter' = %s", SMPCounter);
   } 



   // Mints ONE "GMP" - but ONLY IF the CALLER is "QUALIFIED"!
   function claimGPHP(address claimerAddress) external payable whenNotPaused() {
      console.log("\n>In 'claimGPHP()', Caller Address = %s, 'GMPCounter' = %s", claimerAddress, GMPCounter);

      // 1. Is Claiming ALLOWED yet?
      require(GMPClaimingAllowed == true, "Z1");

      // 2. Any GMPs LEFT to claim - or have they all been claimed already?
      require(GMPCounter < MAX_GMPs, "Z2");

      // 3. Has this address already claimed a GMP?
      require(alreadyMintedGMPHolders[msg.sender] == 0, "Z3");

      // OK to mint:
      _safeMint(msg.sender, GMPCounter);

      emit GMPClaimed(msg.sender, GMPCounter);

      // Add Minter's Address to the ARRAY of GMP Minting Addresses:
      MintPassHoldersArray.push(msg.sender);

      alreadyMintedGMPHolders[msg.sender] = GMPCounter; 

      GMPCounter++;
   }




   function mintSPHP() external payable whenNotPaused() {
      console.log("\n>In 'mintSPHP()' = %s \n>'msg.value' = %s \n>'SMPCounter' = %s", msg.value, SMPCounter);

      // 1. Any SPHP's LEFT available still? Or are we sold out?
      require(SMPCounter < MAX_SMPS, "Z4");
   
      // 2. Has this person already minted a SPHP?
      require(alreadyMintedSMPHolders[msg.sender] == 0, "Z5");

      // 3. Has this person sent enough ETH to buy this SPHP?
      require(msg.value >= SPHPPrice, "Z6");

      // We're good to go:
      _safeMint(msg.sender, SMPCounter);

      emit SMPBought(msg.sender, SMPCounter);

      SMPHoldersArray.push(msg.sender);
      alreadyMintedSMPHolders[msg.sender] = SMPCounter;

      SMPCounter ++;
   }





   function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal whenNotPaused override(ERC721, ERC721Enumerable) {
      super._beforeTokenTransfer(from, to, tokenId);
   }





   // The following functions are overrides required by Solidity:

   function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
      super._burn(tokenId);
   }


   
   
   function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) {
      return super.supportsInterface(interfaceId);
   }




   function pause() public onlyOwner {
      _pause();
   }




   function unpause() public onlyOwner {
      _unpause();
   }




   function getContractBalance() public view onlyOwner returns(uint) {
      return address(this).balance;
   }




   function cashOut(address transferToAddress, uint amountToTransfer) public onlyOwner returns (bool transferSucceeded) {
      console.log("\n\nIn 'cashOut()'\nThe amount getting cashed-out: %s \n-The address that'll be getting the cash: %s", amountToTransfer, transferToAddress);

      require(transferToAddress == contractOwner, "Z7");
      require(amountToTransfer <= address(this).balance, "Z8");

      // Try to "send" the funds back to the Bidder - but wrap in an "if" statement so if it fails, we
      // have recourse: 
      (bool sent, bytes memory data) = payable(msg.sender).call{ value: amountToTransfer } ("");
      // (bool sent, bytes memory data) = payable(msg.sender).call{ value: amountToTransfer } ("");
      require(sent, "Z9");

      data = "";

      // emit CashedOut(amountCashedOut);

      return sent;
      // Originally: return true;

   }






   // Here I'm OVERRIDING the standard "_baseURI()" function that comes in "ERC721.sol" and making it return the 
   // value of "baseTokenURI" - otherwise, it would just return "" - which is what it's written to do out of the box:
   function _baseURI() internal view virtual override returns (string memory) {
      return baseTokenURI;
   }






   // Here I'm OVERRIDING again:
   function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
      // console.log("   -->In 'tokenURI()', inquiring about 'tokenId' = %s", tokenId);

      // Code I'm copy-pasting from the "parent" ERC721.sol Contract:
      require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

      // If "baseURI" is EMPTY, return "", otherwise, concatenate ("") the "baseURI" with the "tokenId":
      return bytes(baseTokenURI).length > 0 ? string(abi.encodePacked(baseTokenURI, tokenId.toString(), ".json")) : "";
   }



   // And in case I need to update the BASE-URI: 
   function updateBaseURI(string calldata newBaseURI) public onlyOwner() {   
      baseTokenURI = newBaseURI; 
      emit BaseURIUpdated(baseTokenURI);
   }




}

NOTE: whatever variables or CONST's you saw used in this contract that you didn't see get declared in this contract, those were declared in the PHPCollectionsMngr contract that's imported into this contract.

4
  • Keep in mind you're importing ERC721.sol and PHPCollectionManager, so PHPMinter will inherit the size of each
    – Ryan Sea
    Commented Aug 22, 2022 at 17:15
  • @RyanSea OK so it's a "sum-game"? Meaning if Contract-A is 5 kb, and Contract-B is 10 kb, then together they’re 15 kb - so if you import them into Contract-C, then Contract-C can’t be more than 9 KB on it's own - cause that'll take the grand total over 24 kb?
    – Mark55
    Commented Aug 22, 2022 at 17:57
  • If that's the case it seems to really defeat the purpose. I thought the whole point was that each Contract can independently be up to 24 kb, so that splitting your code into separate contracts gets you past the 24 kb limit, no?
    – Mark55
    Commented Aug 22, 2022 at 18:01
  • Mentioned this in an answer: you get past the code limit by splitting up contracts and having them interact with eachother through an interface.
    – Ryan Sea
    Commented Aug 22, 2022 at 20:45

1 Answer 1

1

Importing a contract imports the size of the contract. You can try separating the ERC721 contract and the minter contracts, and then having the minter make calls to the ERC721 contract with an interface.

4
  • Well I'm already doing exactly that - meaning separating the ERC721 contract from the Minting contract. Look at the image I included in my original post: it shows the list of all the contracts in my project. The 2 files listed at the bottom are: "ERC721" and "PHPMinter". So there you have it: they're already separated.
    – Mark55
    Commented Aug 22, 2022 at 22:01
  • At least I've now learned that the SIZE of each contract is imported into whatever the importing contract is. Gotta say: this still makes no sense to me. I mean why is everyone always recommending to "split your code into separate contracts!" - if at the end of the day this isn't really a truly effective way of managing the resulting OVERALL size of the final contract? Is there anything I'm still missing here?
    – Mark55
    Commented Aug 22, 2022 at 22:05
  • To reduce the size of the contract you must both split into separate contract & use an interface. PHPMinter literally 'is ...ERC721' , so they're not separate. You're compiling the entire ERC721.sol contract on its own & importing it into PHPMinter, adding to the size of the contract. Spin off the token into a separate contract, and interface with it in PHPMinter through an IERC721.sol
    – Ryan Sea
    Commented Aug 23, 2022 at 1:01
  • oh - ok, I think I get it. Sounds really interesting. Do you know any projects with VERIFIED CODE on etherscan that do this so I can study their code? Or tutorials? (Am super-busy at work so if you happen to know a project off the top of your head - I'd be much obliged! Would literally save me hours of research.)
    – Mark55
    Commented Aug 24, 2022 at 0:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.