6

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 :-( )

7

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.

  • Thanks. I will work in that direction. The only thing is that i wanted to be type strict in the rest of my code without having to create abstract contract for every contract. Do you know if the compiler will integrate in A the bytecode of T if it only use T contract to cast an address created by a factory, declare storage and memory variable of T in A and call function of T ( ie all but the new operator)? – Guenole de Cadoudal Mar 3 '17 at 17:06
  • I'm not sure I understand the question, so this may be way off. I kept the example very simple. You could ensure internal integrity by being strict about which contacts talk to each other. Consider a deployment script that registers the factory addresses with the MegaFactory and transfers ownership to the MegaFactory. Factories could "onlyOwner" the newContract functions. MegaFactory could maintain a registry of trusted contracts. You could enforce a "no talking to strangers" policy throughout the system and get implied strict type casting and interface compatibility. – Rob Hitchens - B9lab Mar 4 '17 at 5:37
  • Thanks Rob, but this was not my point. It was not clear though :-). I post another answer to that question to explain and expose my conclusion. – Guenole de Cadoudal Mar 4 '17 at 14:47
4

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
  • I think you mean to say "Main" does include "Child"; why it's bigger than Child. Posting another answer for consideration. – Rob Hitchens - B9lab Mar 4 '17 at 15:12
  • Ok, let me do an additional test and I will revert – Guenole de Cadoudal Mar 4 '17 at 15:15
  • @RobHitchens see my edit. Is it not what you expected. Did I miss something? – Guenole de Cadoudal Mar 4 '17 at 15:27
  • You're right. It's not what I expected. Possible I've been at this too long :-) Maybe someone can chime in with details of a compiler optimization, etc., to explain that. – Rob Hitchens - B9lab Mar 4 '17 at 15:44
  • I see. maybe worth a tick on my answer then ;-) – Guenole de Cadoudal Mar 4 '17 at 15:45
1

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();
    }
}

This way:

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();
    }
}
0

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.

0

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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