6

According to the official doc, a contract can create a new contract using the new keyword. But it does not work when I test the code from this post. My dev environment is truffle 2.1.1 and testrpc (or geth).

pragma solidity ^0.4.2;

contract MyContract {
    bytes32 public Name;

    function MyContract (bytes32 name) {
        Name = name;
    }
}


contract Factory {
    bytes32[] public Names;
    address[] public newContracts;

    function createContract(bytes32 name) returns(address) {
        address newContract = new MyContract(name);
        newContracts.push(newContract);
        return newContract;
    }

    function getName(uint i) {
        MyContract con = MyContract(newContracts[i]);
        Names[i] = con.Name();
    }
}

When calling Factory.createContract, it sends a new transaction and returns the tx hash instead of contract address.

Factory.deployed().createContract("1234")
// or
Factory.deployed().createContract("1234", {from:web3.eth.accounts[0], gas:200000})

  Transaction: 0x7f92f8bfa88c5707879ca0b8a93e53dbb7919c55fc90d352fb00269d70fd0fdb
  Gas usage: 0x5418
  Block Number: 0x0a
  Block Time: Fri Jan 20 2017 15:16:25 GMT+0800 (CST)

But calling Contract.New gets a new transaction with contract address.

Contract.new("12345")

  Transaction: 0x7275b47ac3712fd26c46b41b1e3f5f2ccbbb638a736e759a48103e08dfccda2a   
  Contract created: 0x1787b9e9e99802c2c1a580ae7e2bcef045e5b7e7   
  Gas usage: 0x019d5f   
  Block Number: 0x09   
  Block Time: Fri Jan 20 2017 15:14:43 GMT+0800 (CST)

What's the correct way to make Factory.createContract work? Any help will be welcomed.

Even tried compiling the contracts in browser-solidity and loading in geth console. But got the same result, a transaction with contractAddress=null.

factory.createContract("1234", {from:eth.accounts[0], gas:200000})

> eth.getTransactionReceipt("0x31cc7dc3b36fa9c4c524fa672f4537d19b5ac8459cc9f8232a073b9607f6d54e")
{
  blockHash: "0x026ecdfec95100d0322bff322c16a3f9df35fac9b6e21106dba1cd6d8c6c7ff5",
  blockNumber: 3029,
  contractAddress: null,
  cumulativeGasUsed: 144490,
  from: "0x5fae6ee92e44ddbf09bc5a9602c14decad10f235",
  gasUsed: 144490,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  root: "0x5481a645efdb0c07d4be3c137e9ef053b3fdb7695a96ce5b6ad92fca1b2edc0e",
  to: "0x2c978a345ed2fe0fc8ecf10b5e00737973f9a07a",
  transactionHash: "0x31cc7dc3b36fa9c4c524fa672f4537d19b5ac8459cc9f8232a073b9607f6d54e",
  transactionIndex: 0
}

Thank @Rob Hitchens for the code. Though the contractAddress is null, but getContractAddressAtIndex returns a valid contract address.

> factory.createContract("hello1", {from:eth.accounts[0], gas:200000})
"0x8e3d1b7d743f935bf4f88fa567292247f583ff22dea4e07ab21806e5546aeacb"
> xx = factory.getContractAddressAtIndex(0)
"0x93936cdf4fcc383679cf0305fb314e18fef620d0"
> eth.getTransactionReceipt("0x8e3d1b7d743f935bf4f88fa567292247f583ff22dea4e07ab21806e5546aeacb")
{
  blockHash: "0x951a2081ba61618820b5def8bec249eb52f829d9932d68555d03a5d4f70b4d42",
  blockNumber: 5,
  contractAddress: null,
  cumulativeGasUsed: 129810,
  gasUsed: 129810,
  logs: [],
  transactionHash: "0x8e3d1b7d743f935bf4f88fa567292247f583ff22dea4e07ab21806e5546aeacb",
  transactionIndex: 0
}

Another issue is it doesn't work in truffle console.

truffle(default)> f = Factory.at(Factory.address)
truffle(default)> f.createContract("hello", {from:web3.eth.accounts[0], gas:200000})
'0xe505daa028d408a800681fba1733777b8ea788cd47f45d28f36a0ff589e12356'
truffle(default)> f.getContractAddressAtIndex(0)
'0x'
  • Have you tried returning an address in createContract? Also, the title seems wrong and should be updated because it asks about "new", but the question asks about "createContract"... – eth Jan 20 '17 at 8:40
  • I have tried that. "createContract" is just a simple wrapper of new statements. It should create new contract, but actually a transaction without contract address. – Yuan Jan 20 '17 at 15:24
  • I just noticed the revisions with the added 3-step truffle console output at the end. You're doing the createContract(), getting a hash, and then proceeding to getContractAddressAtIndex(0). It's not clear that you're waiting for it to mine. Depends on your chain setup to some extent. I feel this is the missing step as it's a little to hard to get one's head around at first. – Rob Hitchens - B9lab Jan 23 '17 at 0:56
4

Agree with @Dtaste about returning the address, but you also need to wait for the transaction to be mined. Have a look at Xavier's getTransactionReceiptMined() function. It will return a promise for the transaction so you can proceed with the knowledge that the transaction is mined and not merely submitted. Before mining, there is only a txnhash and no data.

https://gist.github.com/xavierlepretre/88682e871f4ad07be4534ae560692ee6

For simplicity and troubleshooting, it might be helpful to add a function to return the addresses created as needed.

function getContractAddressAtIndex(uint i) constant returns(address c) {
   return newContracts[i];
}

From the look of your process, this function is going to work if

  1. i is within the bounds of the array (first one is index 0)
  2. the contract really did deploy (no problems with the constructor)
  3. you wait for the createContract() transaction to be mined.

You should be able to verify the correct operation of both the returned address and GetContract...() function with Browser Solidity. You can confirm the contract is working as expected and focus on the way you're interacting with it.

Here's a screen capture of the contract appearing to work:

Solidity Realtime Compiler

Code with the extra function suggested:

 pragma solidity ^0.4.2;

contract MyContract {
    bytes32 public Name;

    function MyContract (bytes32 name) {
        Name = name;
    }
}


contract Factory {
    bytes32[] public names;
    address[] public contracts;

    function createContract(bytes32 name) returns(address) {
        address newContract = new MyContract(name);
        names.push(name);
        contracts.push(newContract);
        return newContract;
    }

    function getName(uint i) constant returns(bytes32 contractName) {
        return names[i];
    }

    function getAddress(uint i) constant returns(address contractAddress) {
        return contracts[i];
    }
}

A verbose and crude truffle test:

// some verbose console.log()s to illustrate what's happening
// $ truffle build
// $ truffle test
// $ should see the first created contract address returned

contract("Factory", function(accounts) {

    var factory;
    var account = accounts[0];

    beforeEach('Deploy a new factory and one "myContract"', function() {
        // deploy a new factory for each test
        return Factory.new("0x1", {from: account})
        .then(function(_factory) {
            factory = _factory;
            // deploy a new myContract so there's something to return in the first slot
            return factory.createContract("0x1", {from: account})
            .then(function(txn) {
                console.log("The txn hash is", txn);
                // let's make sure this is mined before we continue
                return web3.eth.getTransactionReceiptMined(txn);
            })
        })
    })

    it("Should return a myContract address", function() {

        console.log("factory", factory.address);
        // now check the address
        return factory.getAddress.call(0)
        .then(function(_address) {
            console.log("The myContract Address is", _address);
            assert.isAbove(_address,"0x0", "The address is empty");
        })
    })

})

// A handy tool to ensure mining is complete
// https://gist.github.com/xavierlepretre/88682e871f4ad07be4534ae560692ee6

web3.eth.getTransactionReceiptMined = function (txnHash, interval) {
    var transactionReceiptAsync;
    interval = interval ? interval : 500;
    transactionReceiptAsync = function(txnHash, resolve, reject) {
        try {
            var receipt = web3.eth.getTransactionReceipt(txnHash);
            if (receipt == null) {
                setTimeout(function () {
                    transactionReceiptAsync(txnHash, resolve, reject);
                }, interval);
            } else {
                resolve(receipt);
            }
        } catch(e) {
            reject(e);
        }
    };

    if (Array.isArray(txnHash)) {
        var promises = [];
        txnHash.forEach(function (oneTxHash) {
            promises.push(web3.eth.getTransactionReceiptMined(oneTxHash, interval));
        });
        return Promise.all(promises);
    } else {
        return new Promise(function (resolve, reject) {
                transactionReceiptAsync(txnHash, resolve, reject);
            });
    }
};

Hope it helps.

  • I use truffle. Are you writing unit tests, making an app, using the console or something else? – Rob Hitchens - B9lab Jan 21 '17 at 4:03
  • Thanks. Calling getContractAddressAtIndex in truffle console or unit test code both return "0x". It seems the array "newContracts" is empty after calling createContract several times. – Yuan Jan 21 '17 at 4:06
  • I did some further cleanup and fixed an issue with the names[] array. Hopefully clearer now. Added a truffle test that shows how to interact with it. Standard disclaimer ... I didn't spend long on it and it's a little verbose and crude to show what's going on. – Rob Hitchens - B9lab Jan 21 '17 at 5:25
0

If you want to get the address of your new contract instance when calling createContract, you should return it :

 function createContract (bytes32 name) return (address newContract) {
        newContract = new Contract(name);
        newContracts.push(newContract);
 } 
  • Actually I have tried that. But that has no difference. There is not a new contract address. – Yuan Jan 20 '17 at 16:09

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.