Solidity Inheritance – Issues with Multiple Contracts and Inheritance in Solidity

blockchainerc-721soliditytokenstruffle

My smart-contract has gotten bigger than the 24K size allowed by Ethereum, so I had to break it up into smaller contracts. I then had to facilitate all sorts of inheritance and importing between these contracts to make everything work correctly again, but in the end, I was still unable to deploy successfully – and it seems to be for the same "contract-size-is-too-big" reason. Here’s what Truffle is telling me:

Error:  *** Deployment Failed ***

"DBTCFull" ran out of gas (using a value you set in your network config or deployment parameters.)
   * Block limit:  6721975 (0x6691b7)
   * Gas sent:     6721975 (0x6691b7)

I’m pretty sure I’m getting this error cause the contracts are all still too big.

Being that I'm only like 2/3 through this project and still have more code to write, size is going to be an issue. So what I’m really trying to understand here is what approach I should be taking. Should I use:

  1. Inheritance – where contracts import other contracts? or
  2. Reference – where contracts are deployed separately, then reference one another?

Here's a scenario that roughly illustrates how my contracts are supposed to interact with one another:

Contract # 1

contract MakePizza {
    // 1. This contract defines a “Pizza” Struct and…
    // 2. Has functions to make various Types of Pizza

    Struct Pizza { … }
   // etc.
}

Contract # 2

contract DeliverPizza is MakePizza {
    // This contract only handles the Deliveries of Pizzas. So it:
    // 1. Defines a “Customer” Struct (customer’s name, phone number, etc.)
    // 2. Defines a “CustomerAddress” Struct (street address, city, zip-code, etc.) 
    // 3. Has “delivery” type functions
}

Contract # 3

Contract HandleTheMoney is MakePizza, DeliverPizza  {
    // Defines all things having to do with funds:
    // 1. Acceptable Payment methods
    // 2. Defines various Fees
    // 3. Declares the Permissions re who’s allowed to withdraw funds from the contract, etc.
}

// And finally, Contract # 4:

contract JoesPizzaShop is MakePizza, DeliverPizza, HandleTheMoney {
    // The “master” contract, bringing everything together
}

So I’m pretty sure this is the Inheritance approach. (Cause I'm using that is MakePizza syntax here.)

Now here's the thing: this all compiles successfully – but fails to deploy, giving me the error I listed above.
It seems importing all the files into one another isn't doing anything to reduce the overall size.

The other approach would be to deploy each contract separately, then get instances of them all in the “master” contract – the one called JoesPizzaShop – and work that way.

I’m thinking this latter approach would be “cleaner” – and hopefully smaller. But I worry it might also produce its own unique set of nightmares. Might even require major architectural changes.

So basically, I’m not sure how to proceed.

Given that the contract's size seems to be the main issue, which approach should I take?

Am I just executing the inheritance approach the wrong way – or is that just the wrong approach to begin with?

Best Answer

At compilation all contracts are combined together to produce one bytecode so inheritance will not be very helpful to decrease contract size.

From the error message the problem seems to be not enough gas and it appears you are using the default gas 6.7M gas. For reference at this moment mainnet has a block gas limit above 12M gas.

A temporary solution is increasing the block gas limit.

  • For truffle edit the network configuration and add gas parameter.

    networks: {
      development: {
        host: "127.0.0.1",
        port: 8545,
        network_id: "*",
        websockets: true,
        gas: 12000000          // <--- Increased block gas limit
      },
    
  • For ganache-cli pass requested gas to -l parameter:

      ganache-cli -l 12000000
    

An alternative to have separate contract is using libraries to separate functionality. It requires more planning to separate into libraries and adjusting the migrations scripts to deploy and link libraries into main contracts.

pragma solidity >=0.4.0 <0.8.0;
pragma experimental ABIEncoderV2;

library PizzaLib {
    struct Pizza {
        uint256 ingredients;
    }

    function mix(Pizza calldata pizza, uint256 ingredients) external pure returns (Pizza memory) {
        Pizza memory p = pizza;
        p.ingredients = ingredients;
        return p;
    }
}

contract PizzaShop {
    using PizzaLib for PizzaLib.Pizza;
    
    function foo() public pure returns (PizzaLib.Pizza memory) {
        PizzaLib.Pizza memory pizza;
        pizza = pizza.mix(23);
        return pizza;
    }
}

For example migrations will look like

module.exports = async function(deployer, network, accounts) {
  await deployer.deploy(PizzaLib);
  await deployer.link(PizzaLib, PizzaShop);
  await deployer.deploy(PizzaShop);
}

Related Topic