8

I've been struggling a bit with calling methods that change the state of the contract. Take, for example, the following contract:

contract C {

     uint[] numbers;

     function initNumbers() {
         numbers.push(1);
         numbers.push(2);
     }

     function stateChanger(uint a) {
         numbers.push(a);
     }
}

After deploying, the initNumbers() method has to be called/sent as a transaction:

c.initNumbers({from:eth.accounts[0],gas:400000});

due to the fact that it changes the state (executes write operations) on the block chain.

How is it possible to call the second method that has an argument but also changes the state (stores it in the contract)?

I've tried the following but got a BigNumber exception:

c.stateChanger({from:web3.eth.accounts[0],gas:400000}).call(3);

The stack trace was:

/usr/lib/node_modules/web3/node_modules/bignumber.js/bignumber.js:1209
            throw error;
            ^

BigNumber Error: new BigNumber() not a number: [object Object]
    at raise (/usr/lib/node_modules/web3/node_modules/bignumber.js/bignumber.js:1177:25)
    at /usr/lib/node_modules/web3/node_modules/bignumber.js/bignumber.js:1165:33
    at new BigNumber (/usr/lib/node_modules/web3/node_modules/bignumber.js/bignumber.js:193:67)
    at new BigNumber (/usr/lib/node_modules/web3/node_modules/bignumber.js/bignumber.js:203:25)
    at toBigNumber (/usr/lib/node_modules/web3/lib/utils/utils.js:367:12)
    at Object.toTwosComplement (/usr/lib/node_modules/web3/lib/utils/utils.js:378:21)
    at formatInputInt [as _inputFormatter] (/usr/lib/node_modules/web3/lib/solidity/formatters.js:40:38)
    at SolidityType.encode (/usr/lib/node_modules/web3/lib/solidity/type.js:179:17)
    at /usr/lib/node_modules/web3/lib/solidity/coder.js:86:29
    at Array.map (native)
6

Here's your function slightly modified by adding public to numbers so we can peek at the data:

contract C {    
     uint[] public numbers;

     function initNumbers() {
         numbers.push(1);
         numbers.push(2);
     }

     function stateChanger(uint a) {
         numbers.push(a);
     }    
}

Using the stripCrLf script in How to load Solidity source file into geth, I've flattened the source code using the following command:

user@Kumquat:~$ echo "var cSource='`stripCrLf C.sol`'"
var cSource='contract C { uint[] public numbers; function initNumbers() { numbers.push(1); numbers.push(2); } function stateChanger(uint a) { numbers.push(a); }      }'

I'm running a dev network using the following command (I have my password in passwordfile):

user@Kumquat:~$ geth --datadir ~/devdata --dev --mine --minerthreads 1 --unlock 0 --password ~/passwordfile console

I paste the flattened code in the geth command line:

> var cSource='contract C { uint[] public numbers; function initNumbers() { numbers.push(1); numbers.push(2); } function stateChanger(uint a) { numbers.push(a); }      }'
undefined

And compile the code using the following command:

> var cCompiled = web3.eth.compile.solidity(cSource);
Version: 0.3.5-0/RelWithDebInfo-Linux/g++/Interpreter

path: /usr/bin/solc
undefined

Here is the Application Binary Interface:

> cCompiled.C.info.abiDefinition
[{
    constant: false,
    inputs: [],
    name: "initNumbers",
    outputs: [],
    type: "function"
}, {
    constant: false,
    inputs: [{
        name: "a",
        type: "uint256"
    }],
    name: "stateChanger",
    outputs: [],
    type: "function"
}, {
    constant: true,
    inputs: [{
        name: "",
        type: "uint256"
    }],
    name: "numbers",
    outputs: [{
        name: "",
        type: "uint256"
    }],
    type: "function"
}]

Deploy the contract on the blockchain:

> var cContract = web3.eth.contract(cCompiled.C.info.abiDefinition);
undefined
> var c = cContract.new({
    from:web3.eth.accounts[0], 
    data: cCompiled.C.code, gas: 400000}, 
    function(e, contract) {
      if (!e) {
        if (!contract.address) {
          console.log("Contract transaction send: TransactionHash: " + 
            contract.transactionHash + " waiting to be mined...");
        } else {
          console.log("Contract mined! Address: " + contract.address);
          console.log(contract);
        }
    }
})
Contract mined! Address: 0x672807a8c0f72a52d759942e86cfe33264e73934

Call your initNumbers():

> c.initNumbers({from:eth.accounts[0], gas: 400000})
"0x3f21e1cdb636a2cf291cd9296282a4e9c4f4ed57c65a13fedc937a97203c3a75"
...
> c.numbers(0)
1
> c.numbers(1)
2
> c.numbers(2)
0


Calling stateChanger(...) - Method 1

> c.stateChanger(3, {from:eth.accounts[0], gas: 400000})
"0x3ecf3aa6464f93d5b062ad01b76b5dbc4302c190baf70d157a4d7607d4c7c749"
...
> c.numbers(2)
3
> c.numbers(3)
0


Calling stateChanger(...) - Method 2

> c.stateChanger.sendTransaction(4, {from: eth.accounts[0], gas: 400000})
"0xe1d9c57ca55dbdf446e55602570888ff5efce97a91ddc5c9575d10c7c9a1f0c8"
...
> c.numbers(3)
4
> c.numbers(4)
0


Calling stateChanger(...) - Method 3

And following is how to use the raw data sendTransaction(...) format.

Find the function signature. Note that the uint parameter is actually a uint256 (see the ABI format above):

> web3.sha3('stateChanger(uint256)').substr(0, 10)
"0x65060775"

Here is 5 encoded in hex and padded to 32 bytes:

0x0000000000000000000000000000000000000000000000000000000000000005

Join the data together, removing the 0x from the hex(5) string:

0x650607750000000000000000000000000000000000000000000000000000000000000005

Here's how to sendTransaction(...). The to: parameter is the address of the mined contract:

> var result = web3.eth.sendTransaction({
    from: eth.accounts[0], 
    to: "0x672807a8c0f72a52d759942e86cfe33264e73934", 
    data: "0x650607750000000000000000000000000000000000000000000000000000000000000005", 
    gas: 400000}
)
undefined
> result
"0x420d4256718b4326967677da2e460a1d361a62f3ceee386e1313d25b12c0f610"
...
> c.numbers(4)
5
> c.numbers(5)
0


Bonus 1 - estimateGas(...)

Since we have worked out the data to send with sendTransaction(...) above.

Here is the gas used by the sendTransaction(...) call above:

> eth.getTransactionReceipt(result).gasUsed
46888

You can use the same data format to estimate the gas using the estimateGas(...) call:

var result = web3.eth.estimateGas({
    from: eth.accounts[0], 
    to: "0x672807a8c0f72a52d759942e86cfe33264e73934", 
    data: "0x650607750000000000000000000000000000000000000000000000000000000000000005", 
    gas: 400000}
)
undefined
> result
46888


Bonus 2 - The Transaction Data

Set debug.verbosity(7) and watch the transaction get removed from the transaction pool after it is executed:

> debug.verbosity(7)
...
I0730 00:13:43.918828 core/tx_pool.go:547] removed tx (
    TX(07c3c717c87ce1e2b31ba144b6b751d66c90a8a69375478a5ef4e60a6f42996c)
    Contract: false
    From:     a7857047907d53a2e494d5f311b4b586dc6a96d2
    To:       672807a8c0f72a52d759942e86cfe33264e73934
    Nonce:    35
    GasPrice: 20000000000
    GasLimit  400000
    Value:    0
    Data:     0x650607750000000000000000000000000000000000000000000000000000000000000004
    V:        0x1c
    R:        0x75ff67a969670a0f35ae415f2fa541446a71fa9b7d8ad6247ad12fd7ae3986a
    S:        0x7150424ed1698792cdcf1253a79f2474e4df411610db97e36240bab50af6c80c
    Hex:      f889238504a817c80083061a8094672807a8c0f72a52d759942e86cfe33264e7393480a46506077500000000000000000000000000000000000000000000000000000000000000041ca0075ff67a969670a0f35ae415f2fa541446a71fa9b7d8ad6247ad12fd7ae3986aa07150424ed1698792cdcf1253a79f2474e4df411610db97e36240bab50af6c80c
) from pool: low tx nonce or out of funds

The different methods of calling (c.stateChanger(3, {from:eth.accounts[0], gas: 400000}) and c.stateChanger.sendTransaction(4, {from: eth.accounts[0], gas: 400000})) are converted to the sendTransaction(...) format.

  • 1
    I was about to post the answer. I found it by trial and error :)) . You always have to specify the sender/fee tuple alongside the arguments of the method. Thanks for stripCrLf. – Sebi Jul 29 '16 at 13:41
  • 2
    @Sebi go ahead and post your answer. It's the idea of stack exchange network sites to have more than one answer, even if they are almost the same. This way, users cab vote up the best ones to the top. – Afr Jul 31 '16 at 5:37
  • Done. I've posted my answer. – Sebi Aug 1 '16 at 8:05
  • 1
    I guess there is a typo on 0x650607750000000000000000000000000000000000000000000000000000000000000004 should be 0x650607750000000000000000000000000000000000000000000000000000000000000005, right? – Juan Ignacio Pérez Sacristán Oct 13 '16 at 13:48
  • How to do it using Infura and web3@1.0? – kitsune Feb 16 at 4:43
1

My answer is similar to Bokky's:

cContract = web3.eth.contract(cContractABI).at(cContractEthAddress);

cContract.initAddrs({from:web3.eth.accounts[0],gas:utils.DEFAULT_TX_COST});

where cContract is a Javascript object that defines a contract. A key note is to make sure that the account invoking the state changing method always has enough balance otherwise method invocation will abort and Ether will be lost.

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.