Need help to break down large contract

by Guenole de Cadoudal   Last Updated November 08, 2018 01:28 AM

related to question Best practices to solve the problem of a contract that is too large?, I am facing similar issue (contract too large to be deployed, compare to gasLimit of the blockchain) and before I launch a refactoring of my code I am seeking advise on the possible approach.

Here is my current design:

contract A {
    // some state variables but not so many
    function A(some params) {
       // initialize the contract
    }
    function newT(some params) {
       // create a new T instance and store it in A's state variable
    } 
    // several other functions with business logic
}

contract T {
    // some state variables but not so many
    function T(A a, some params) {
       // test that it is only called by A, checking msg.sender
       // initialize the contract
    }
    function newU(some params) {
       // create a new U instance and store it in T's state variable
    } 
    // some business logic as functions
}

contract U {
    // some state variables but not so many
    function U(T t, some params) {
       // test that it is only called by T, checking msg.sender
       // initialize the contract
    }
    // some business logic as functions
}

When creating the fist instance of A, the code is massive. Am I correct in understanding that the code of A will include the full code of A, T and U (so it is able to create new instance of T, respectively U)?

If so, does it mean that I am not using a valid factory pattern for Ethereum as the factory must contain the full code of its instantiated classes ?

What is therefore the best factory pattern when we have real business contract that do embed real business logic? (and still I am in a proof of concept at this stage :-( )



Answers 5


You can address most use cases using Solidity feature called Libraries:

http://solidity.readthedocs.io/en/develop/contracts.html#libraries

You split your code to different libraries. Libraries do not need to be deployed in the same transaction, but you can do a chunked deployment. You can even reuse the deployed library across different contracts.

Mikko Ohtamaa
Mikko Ohtamaa
March 03, 2017 12:42 PM

You're correct. Factories contain the bytecode of the contracts they deploy. It doesn't mean you're not using factories correctly, but it does mean you need to break the accumulation of bytecode in A (a.k.a. MegaFactory).

Libraries might be appropriate, but you can also build a MegaFactory factory without blowing the gas budget. Just structure it slightly differently.

Instead of having A be a massive factory with all the bytecode for all the contracts, have a little factory for each species. So, FactoryT and FactoryU. The factories will be only slightly larger than the bytecode they deploy.

You can still have a MegaFactory that does it all. It would use interface contracts that provide just enough information to understand the ABIs for the actual factories it talks to. Something like this:

contract FactoryTInterface {
  function newT() returns(address newTContract);
}

contract MegaFactory is FactoryTInterface {

  address factoryTaddress;

  function MegaFactory(address factoryT) {
      factoryTaddress = factoryT;
  }

  function newT() returns(address newContract) {
    FactoryTInterface factory = FactoryTInterface(factoryTaddress);
    return factory.newT();
  }
}

contract FactoryT {

  function newT() returns(address newT) {
    T t = new T();
    return t;
  }
}

contract T {
  // do something :-)
}

The MegaFactory isn't absorbing all the bytecode for a T because it isn't concerned with actually deploying one. It gets all the information it needs from the Interface contract; just enough to send a request to the real factory.

Hope it helps.

Rob Hitchens B9lab
Rob Hitchens B9lab
March 03, 2017 14:02 PM

Complementary answer to the Factory usage.

Doing some testing on the size of the compiled code, I found out that the compiler only include the code of the created contract if the new operator is called on the contract.

If we consider the 3 contracts below with enough content to compare:

contract Child {
    uint i1;
    uint i2;
    uint i3;
    uint i4;
    uint i5;

    function f1() { i1=1;   }
    function f2() { i2=2;   }
    function f3() { i3=3;   }
    function f4() { i4=4;   }
    function f5() { i5=5;   }
}

contract MainFactory {
    Child ch;

    function MainFactory() {
        ch = new Child();
    }

    function test() {
        ch.f1();
        ch.f2();
        ch.f3();
        ch.f4();
        ch.f5();
    }
}

contract Main {
    Child ch;
    function Main(address child) {
        ch=(Child(child));
    }
    function test() {
        ch.f1();
        ch.f2();
        ch.f3();
        ch.f4();
        ch.f5();
    }
}

When compiled with solc we have

  • Code length of Child = 518
  • Code length of Main = 1384
  • Code length of MainFactory = 1970

And when testing if the code of Child is included in the code of both other contract we have that

  • Main does not include Child bytecode
  • MainFactory does have the Child bytecode appended at the end of its.

Conclusion: Using MetaFactory like Rob suggested enable to have the Main contract not grow in size when the Child contract grows while keeping compilation able to check the types, function calls, etc without having to implement abstract contracts.

--- EDITED to further demonstrate ---

Now I change the Child contract, adding more functions to make its byte code grow:

contract Child {
    uint i1;
    uint i2;
    uint i3;
    uint i4;
    uint i5;

    function f1() { i1=1;   }
    function f2() { i2=2;   }
    function f3() { i3=3;   }
    function f4() { i4=4;   }
    function f5() { i5=5;   }

    function f1_() { i1=1;  }
    function f2_() { i2=2;  }
    function f3_() { i3=3;  }
    function f4_() { i4=4;  }
    function f5_() { i5=5;  }
}

Now after compilation we have:

  • Code length of Child = 942
  • Code length of Main = 1384 (no change)
  • Code length of MainFactory = 2394

Also, if I search the bytecode of Child in both other contract's byte code

> DictContract["Child"].code
"0x6060604052341561000c57fe5b5b6101ba8061001c6000396000f300606060405236156100885763ffffffff60e060020a600035041663070d4270811461008a5780633c9d377d1461009c578063518efaf01461009c5780639942ec6f146100c0578063a7b4581d146100d2578063aaf05f3d1461008a578063af09d814146100c0578063c27fc30514610108578063c2ce99ea14610108578063c3f90202146100d2575bfe5b341561009257fe5b61009a61013e565b005b34156100a457fe5b61009a610146565b005b34156100a457fe5b61009a610146565b005b34156100c857fe5b61009a610156565b005b34156100da57fe5b61009a61015e565b005b341561009257fe5b61009a61013e565b005b34156100c857fe5b61009a610156565b005b341561011057fe5b61009a610176565b005b341561011057fe5b61009a610176565b005b34156100da57fe5b61009a61015e565b005b60036002555b565b60056004555b565b60056004555b565b60026001555b565b60046003555b565b60036002555b565b60026001555b565b60016000555b565b60016000555b565b60046003555b5600a165627a7a7230582072fe9922ad7ce90140824c40e32acecfdcd85c50682c9d410778319d9e10cb4e0029"
> DictContract["Main"].code
"0x6060604052341561000c57fe5b6040516020806102b383398101604052515b60008054600160a060020a031916600160a060020a0383161790555b505b6102688061004b6000396000f300606060405263ffffffff60e060020a600035041663f8a8fd6d8114610021575bfe5b341561002957fe5b610031610033565b005b60008054604080517fc27fc3050000000000000000000000000000000000000000000000000000000081529051600160a060020a039092169263c27fc3059260048084019382900301818387803b151561008957fe5b60325a03f1151561009657fe5b505060008054604080517f9942ec6f0000000000000000000000000000000000000000000000000000000081529051600160a060020a039092169350639942ec6f92600480830193919282900301818387803b15156100f157fe5b60325a03f115156100fe57fe5b505060008054604080517faaf05f3d0000000000000000000000000000000000000000000000000000000081529051600160a060020a03909216935063aaf05f3d92600480830193919282900301818387803b151561015957fe5b60325a03f1151561016657fe5b505060008054604080517fc3f902020000000000000000000000000000000000000000000000000000000081529051600160a060020a03909216935063c3f9020292600480830193919282900301818387803b15156101c157fe5b60325a03f115156101ce57fe5b505060008054604080517f3c9d377d0000000000000000000000000000000000000000000000000000000081529051600160a060020a039092169350633c9d377d92600480830193919282900301818387803b151561022957fe5b60325a03f1151561023657fe5b5050505b5600a165627a7a72305820f58668157b5128d3af3ed23b8e804e37b8940af2111d63810108e3100c6afef60029"
> DictContract["MainFactory"].code
"0x6060604052341561000c57fe5b5b61001561004f565b60405190819003906000f080151561002957fe5b60008054600160a060020a031916600160a060020a03929092169190911790555b61005f565b6040516101d6806102d683390190565b6102688061006e6000396000f300606060405263ffffffff60e060020a600035041663f8a8fd6d8114610021575bfe5b341561002957fe5b610031610033565b005b60008054604080517fc27fc3050000000000000000000000000000000000000000000000000000000081529051600160a060020a039092169263c27fc3059260048084019382900301818387803b151561008957fe5b60325a03f1151561009657fe5b505060008054604080517f9942ec6f0000000000000000000000000000000000000000000000000000000081529051600160a060020a039092169350639942ec6f92600480830193919282900301818387803b15156100f157fe5b60325a03f115156100fe57fe5b505060008054604080517faaf05f3d0000000000000000000000000000000000000000000000000000000081529051600160a060020a03909216935063aaf05f3d92600480830193919282900301818387803b151561015957fe5b60325a03f1151561016657fe5b505060008054604080517fc3f902020000000000000000000000000000000000000000000000000000000081529051600160a060020a03909216935063c3f9020292600480830193919282900301818387803b15156101c157fe5b60325a03f115156101ce57fe5b505060008054604080517f3c9d377d0000000000000000000000000000000000000000000000000000000081529051600160a060020a039092169350633c9d377d92600480830193919282900301818387803b151561022957fe5b60325a03f1151561023657fe5b5050505b5600a165627a7a72305820908047bc9b21dceed16a5af60990e083fa13908ecf84b3f9f3cf73cb0adc7cf000296060604052341561000c57fe5b5b6101ba8061001c6000396000f300606060405236156100885763ffffffff60e060020a600035041663070d4270811461008a5780633c9d377d1461009c578063518efaf01461009c5780639942ec6f146100c0578063a7b4581d146100d2578063aaf05f3d1461008a578063af09d814146100c0578063c27fc30514610108578063c2ce99ea14610108578063c3f90202146100d2575bfe5b341561009257fe5b61009a61013e565b005b34156100a457fe5b61009a610146565b005b34156100a457fe5b61009a610146565b005b34156100c857fe5b61009a610156565b005b34156100da57fe5b61009a61015e565b005b341561009257fe5b61009a61013e565b005b34156100c857fe5b61009a610156565b005b341561011057fe5b61009a610176565b005b341561011057fe5b61009a610176565b005b34156100da57fe5b61009a61015e565b005b60036002555b565b60056004555b565b60056004555b565b60026001555b565b60046003555b565b60036002555b565b60026001555b565b60016000555b565b60016000555b565b60046003555b5600a165627a7a7230582072fe9922ad7ce90140824c40e32acecfdcd85c50682c9d410778319d9e10cb4e0029"


> DictContract["MainFactory"].code.indexOf(DictContract["Child"].code.replace("0x",""))
1454   <-- Found
> DictContract["Main"].code.indexOf(DictContract["Child"].code.replace("0x",""))
-1     <-- Not found
Guenole de Cadoudal
Guenole de Cadoudal
March 04, 2017 14:57 PM

Can reduce the size of Main by including only the Child Interface, and not the entire Child.

contract Child {
    uint i1;
    uint i2;
    uint i3;
    uint i4;
    uint i5;

    function f1() { i1=1;   }
    function f2() { i2=2;   }
    function f3() { i3=3;   }
    function f4() { i4=4;   }
    function f5() { i5=5;   }
}

contract MainFactory {
    Child ch;

    function MainFactory() {
        ch = new Child();
    }

    function test() {
        ch.f1();
        ch.f2();
        ch.f3();
        ch.f4();
        ch.f5();
    }
}

contract ChildInterface {
    function f1() {}
    function f2() {}
    function f3() {}
    function f4() {}
    function f5() {}
}

contract Main {
    ChildInterface ch;
    function Main(address child) {
        ch=ChildInterface(child);
    }
    function test() {
        ch.f1();
        ch.f2();
        ch.f3();
        ch.f4();
        ch.f5();
    }
}
Rob Hitchens B9lab
Rob Hitchens B9lab
March 04, 2017 15:13 PM

I suggest implementing ERC1538: Transparent Contract Standard. It is a contract architecture that gets around Ethereum's contract size limit.

mudge
mudge
November 08, 2018 00:59 AM

Related Questions



Where does Ethereum Dapps go in known design paterns

Updated February 26, 2018 16:28 PM

Upgradeable smart contracts

Updated October 01, 2017 15:28 PM


Design pattern for game development

Updated November 30, 2018 18:28 PM