Solidity Syntax – Calling Contract State Changing Methods

contract-designcontract-developmentcontract-invocationsolidity

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)

Best Answer

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.

Related Topic