Skip to content

Workshop #53 Raw JavaScript Smart Contracts Testing

BokkyPooBah edited this page May 9, 2018 · 8 revisions

Notes to go with #53 [Intermediate] Raw JavaScript Smart Contracts Testing & Continuation of #50.



Table Of Contents



References



Prerequisites



Directories And Files

At the end of this workshop, your directory structure and files should resemble the following list, where ~/Projects can be substituted for your workspace:

~/Projects/Workshop53
  ~/Projects/Workshop53/contracts
    Workshop.sol
  ~/Projects/Workshop53/test
    00_runGeth.sh
    01_runTest1.sh
    02_attach.sh


Run geth in --dev mode

In ~/Projects/Workshop53/test, run the following command, and type exit to return to your terminal command prompt. This will run geth in --dev mode with a block time of 1 second, and the blockchain data will be stored in ~/Projects/Workshop53/test/testchain subdirectory.

$ geth --dev --dev.period 1 --datadir ./testchain console
INFO [05-09|03:05:51] Maximum peer count                       ETH=25 LES=0 total=25
INFO [05-09|03:05:53] Using developer account                  address=0x223b07a19170754cf6414421c8c8633A47Bb1A0d
INFO [05-09|03:05:53] Starting peer-to-peer node               instance=Geth/v1.8.6-stable-12683fec/darwin-amd64/go1.10
INFO [05-09|03:05:53] Allocated cache and file handles         database=/Users/bok/Projects/Workshop53/test/testchain/geth/chaindata cache=768 handles=128
INFO [05-09|03:05:53] Writing custom genesis block 
...
INFO [05-09|03:05:53] Etherbase automatically configured       address=0x223b07a19170754cf6414421c8c8633A47Bb1A0d
INFO [05-09|03:05:53] Starting mining operation 
INFO [05-09|03:05:53] Commit new mining work                   number=1 txs=0 uncles=0 elapsed=347.117µs
INFO [05-09|03:05:53] Successfully sealed new block            number=1 hash=60833f…cfbd4f
INFO [05-09|03:05:53] 🔨 mined potential block                  number=1 hash=60833f…cfbd4f
INFO [05-09|03:05:53] Commit new mining work                   number=2 txs=0 uncles=0 elapsed=351.32µs
INFO [05-09|03:05:54] Successfully sealed new block            number=2 hash=e58da5…aab844
INFO [05-09|03:05:54] 🔨 mined potential block                  number=2 hash=e58da5…aab844
> eth.accounts;
["0x223b07a19170754cf6414421c8c8633a47bb1a0d"]
> exit
INFO [05-09|03:06:06] IPC endpoint closed                      endpoint=/Users/bok/Projects/Workshop53/test/testchain/geth.ipc
...
INFO [05-09|03:06:06] whisper stopped 

Before issuing the exit command, you can try out other commands like those listed below:

> eth.getBalance(eth.accounts[0])
1.15792089237316195423570985008687907853269984665640564039457584007913129639927e+77
> eth.blockNumber
233


Create 00_runGeth.sh Script To Execute geth

Create the file 00_runGeth.sh in your ~/Projects/Workshop53/test subdirectory with the following contents:

#!/bin/sh

# Remove old blockchain data
rm -f ./testchain/geth/chaindata/*

geth --dev --dev.period 1 --datadir ./testchain console

After you have saved the file, run the following command to turn on the executable bit:

chmod u+x 00_runGeth.sh

To execute your 00_runGeth.sh script, type the following command in your terminal command line:

./00_runGeth.sh


Create 01_runTest1.sh To Execute The Test

Create the file 01_runTest1.sh in your ~/Projects/Workshop53/test subdirectory with the following contents:

#!/bin/sh

geth --datadir ./testchain attach

After you have saved the file, run the following command to turn on the executable bit:

chmod u+x 01_runTest1.sh

We will later add contents to this file to execute our desired tests.



Create 02_attach.sh To Attach To The geth Instance

Create the file 02_attach.sh in your ~/Projects/Workshop53/test subdirectory by copying 01_runTest1.sh into a new 02_attach.sh file so we can use this to attach to the geth node.

You can type ./02_attach.sh to attach to the geth instance started by the 00_runGeth.sh script.

Open up a terminal and change to the ~/Projects/Workshop53/test subdirectory. Run ./00_runGeth.sh and leave this process running. You can later shut down the this process by typing exit in the geth command line.

Open up a second terminal and change to the ~/Projects/Workshop53/test subdirectory. You can test out the ./01_runTest1.sh and ./02_attach.sh scripts.



Create A Sample Workshop.sol Smart Contract

Create a sample Workshop.sol smart contract in your ~/Projects/Workshop53/contracts subdirectory with the following contents:

pragma solidity ^0.4.23;

contract Workshop {

    string public var1;
    uint public var2;

    constructor () public {
        var1 = "hello";
        var2 = 123;
    }

    function setVar1(string _var1) public {
        var1 = _var1;
    }

    function setVar2(uint _var2) public {
        var2 = _var2;
    }
}

You can paste the content of this file into http://remix.ethereum.org to test it out.



Customise 01_runTest1.sh To Only Display Data Of Interest

We now modify our 01_runTest1.sh testing script to extract only the data we want to see:

#!/bin/sh

# Run `geth` with the commands between the `EOF` markers, searching for lines containing the `RESULT: ` text and remove this `RESULT: ` text from the displayed lines.
geth --datadir ./testchain attach << EOF | grep "RESULT: " | sed "s/RESULT: //"
// geth --datadir ./testchain attach << EOF

// Display the first account
console.log("RESULT: eth.accounts[0]: " + eth.accounts[0]);

// Display the balance of the first account
console.log("RESULT: balance: " + web3.fromWei(eth.getBalance(eth.accounts[0]), "ether") + " ETH");

// Display some text
console.log("RESULT: hello");

EOF

You can now run this script ./01_runTest1.sh to display the following text:

$ ./01_runTest1.sh
eth.accounts[0]: 0x223b07a19170754cf6414421c8c8633a47bb1a0d
balance: 1.15792089237316195423570985008687907853269984665640564039457584007913129639927e+59 ETH
hello


Customise 01_runTest1.sh To Execute Your Contract Deployment And Tests

Replace the contents of 01_runTest1.sh with the following commands to compile and extract the Application Binary Interface (ABI) and bytecode for your smart contract, deploy your contract, send transactions to your contract to update the variables and retrieve data from your deployed smart contract:

#!/bin/sh

# Assign filename to the $CONTRACT environment variable
CONTRACT=Workshop.sol

# Copy the $CONTRACT from the ../contracts/ subdirectory into the ../test/ subdirectory
cp ../contracts/$CONTRACT .

# Compile the Solidity file and save the ABI and bytecode into Workshop.js
echo "var workshopOutput=`solc --optimize --pretty-json --combined-json abi,bin,interface $CONTRACT`;" > Workshop.js

geth --datadir ./testchain attach << EOF | grep "RESULT: " | sed "s/RESULT: //"
// geth --datadir ./testchain attach << EOF

// Load the generated ABI and bytecode
loadScript("Workshop.js");

// Display some information
console.log("RESULT: " + JSON.stringify(workshopOutput));

# Extract the contract ABI
var workshopAbi = JSON.parse(workshopOutput.contracts["$CONTRACT:Workshop"].abi);

# Extract the contract bytecode
var workshopBin = "0x" + workshopOutput.contracts["$CONTRACT:Workshop"].bin;

# Display both values
console.log("RESULT: workshopAbi=" + JSON.stringify(workshopAbi));
console.log("RESULT: workshopBin=" + JSON.stringify(workshopBin));


// -----------------------------------------------------------------------------
var deployWorkshopMessage = "Deploy Workshop";
// -----------------------------------------------------------------------------
console.log("RESULT: --- " + deployWorkshopMessage + " ---");
var workshopContract = web3.eth.contract(workshopAbi);
console.log("RESULT: " + JSON.stringify(workshopContract));

var workshopTx = null;
var workshopAddress = null;
var workshop = workshopContract.new({from: eth.accounts[0], data: workshopBin, gas: 4000000},
  function(e, contract) {
    if (!e) {
      if (!contract.address) {
        workshopTx = contract.transactionHash;
      } else {
        workshopAddress = contract.address;
        console.log("DATA: workshopAddress=" + workshopAddress);
      }
    }
  }
);

// Wait until there are no pending transactions in the txpool
while (txpool.status.pending > 0) {
}

// Display the values of var1 and var2
console.log("RESULT: var1=" + workshop.var1());
console.log("RESULT: var2=" + workshop.var2());
console.log("RESULT: ");

// -----------------------------------------------------------------------------
var modifyVarssMessage = "Modifying Vars";
// -----------------------------------------------------------------------------
console.log("RESULT: --- " + modifyVarssMessage + " ---");
var modifyVars1Tx = workshop.setVar1("Hello, World!4567890123456789012345", {from: eth.accounts[0], gas: 100000});
var modifyVars2Tx = workshop.setVar2(456, {from: eth.accounts[0], gas: 100000});
while (txpool.status.pending > 0) {
}

// Display the transaction status
console.log("RESULT: modifyVars1Tx=" + JSON.stringify(eth.getTransaction(modifyVars1Tx)));
console.log("RESULT: modifyVars1TxReceipt=" + JSON.stringify(eth.getTransactionReceipt(modifyVars1Tx)));
console.log("RESULT: modifyVars2Tx=" + JSON.stringify(eth.getTransaction(modifyVars2Tx)));
console.log("RESULT: modifyVars2TxReceipt=" + JSON.stringify(eth.getTransactionReceipt(modifyVars2Tx)));

// Display the new values of var1 and var2
console.log("RESULT: var1=" + workshop.var1());
console.log("RESULT: var2=" + workshop.var2());
console.log("RESULT: ");

EOF

You can now execute the ./01_runTest1.sh command to generate the following output:

$ ./01_runTest1.sh
{"contracts":{"Workshop.sol:Workshop":{"abi":"[{\"constant\":true,\"inputs\":[],\"name\":\"var2\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_var2\",\"type\":\"uint256\"}],\"name\":\"setVar2\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"var1\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_var1\",\"type\":\"string\"}],\"name\":\"setVar1\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]","bin":"608060405234801561001057600080fd5b506040805180820190915260058082527f68656c6c6f000000000000000000000000000000000000000000000000000000602090920191825261005591600091610060565b50607b6001556100fb565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a157805160ff19168380011785556100ce565b828001600101855582156100ce579182015b828111156100ce5782518255916020019190600101906100b3565b506100da9291506100de565b5090565b6100f891905b808211156100da57600081556001016100e4565b90565b6103018061010a6000396000f3006080604052600436106100615763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663507aff57811461006657806364d7a1e51461008d57806367e919b6146100a7578063f37c0c2414610131575b600080fd5b34801561007257600080fd5b5061007b61018a565b60408051918252519081900360200190f35b34801561009957600080fd5b506100a5600435610190565b005b3480156100b357600080fd5b506100bc610195565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100f65781810151838201526020016100de565b50505050905090810190601f1680156101235780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561013d57600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526100a59436949293602493928401919081908401838280828437509497506102239650505050505050565b60015481565b600155565b6000805460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152929183018282801561021b5780601f106101f05761010080835404028352916020019161021b565b820191906000526020600020905b8154815290600101906020018083116101fe57829003601f168201915b505050505081565b805161023690600090602084019061023a565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061027b57805160ff19168380011785556102a8565b828001600101855582156102a8579182015b828111156102a857825182559160200191906001019061028d565b506102b49291506102b8565b5090565b6102d291905b808211156102b457600081556001016102be565b905600a165627a7a723058207f1d64d57c3f34b032eb67cc41ceee42edb1a77183d77ac7ce733ced3827ff5d0029"}},"version":"0.4.23+commit.124ca40d.Darwin.appleclang"}
workshopAbi=[{"constant":true,"inputs":[],"name":"var2","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_var2","type":"uint256"}],"name":"setVar2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"var1","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_var1","type":"string"}],"name":"setVar1","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]
workshopBin="0x608060405234801561001057600080fd5b506040805180820190915260058082527f68656c6c6f000000000000000000000000000000000000000000000000000000602090920191825261005591600091610060565b50607b6001556100fb565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a157805160ff19168380011785556100ce565b828001600101855582156100ce579182015b828111156100ce5782518255916020019190600101906100b3565b506100da9291506100de565b5090565b6100f891905b808211156100da57600081556001016100e4565b90565b6103018061010a6000396000f3006080604052600436106100615763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663507aff57811461006657806364d7a1e51461008d57806367e919b6146100a7578063f37c0c2414610131575b600080fd5b34801561007257600080fd5b5061007b61018a565b60408051918252519081900360200190f35b34801561009957600080fd5b506100a5600435610190565b005b3480156100b357600080fd5b506100bc610195565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100f65781810151838201526020016100de565b50505050905090810190601f1680156101235780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561013d57600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526100a59436949293602493928401919081908401838280828437509497506102239650505050505050565b60015481565b600155565b6000805460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152929183018282801561021b5780601f106101f05761010080835404028352916020019161021b565b820191906000526020600020905b8154815290600101906020018083116101fe57829003601f168201915b505050505081565b805161023690600090602084019061023a565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061027b57805160ff19168380011785556102a8565b828001600101855582156102a8579182015b828111156102a857825182559160200191906001019061028d565b506102b49291506102b8565b5090565b6102d291905b808211156102b457600081556001016102be565b905600a165627a7a723058207f1d64d57c3f34b032eb67cc41ceee42edb1a77183d77ac7ce733ced3827ff5d0029"
--- Deploy Workshop ---
{"abi":[{"constant":true,"inputs":[],"name":"var2","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_var2","type":"uint256"}],"name":"setVar2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"var1","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_var1","type":"string"}],"name":"setVar1","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}],"eth":{"_requestManager":{"polls":{},"provider":{},"timeout":null},"accounts":["0x223b07a19170754cf6414421c8c8633a47bb1a0d"],"blockNumber":4653,"coinbase":"0x223b07a19170754cf6414421c8c8633a47bb1a0d","compile":{},"gasPrice":"1","hashrate":0,"mining":true,"pendingTransactions":[],"protocolVersion":"0x3f","syncing":false}}
var1=hello
var2=123

--- Modifying Vars ---
modifyVars1Tx={"blockHash":"0xed71e433960c495dd5dc840d1a6f0dc5a5fd8b1ccad6cf10b7be02915a0ee793","blockNumber":4657,"from":"0x223b07a19170754cf6414421c8c8633a47bb1a0d","gas":100000,"gasPrice":"1","hash":"0x8865ef50938dfeee0b5a122f85be11476e0a31d4ccc7b71eb2ca64c3e09449d6","input":"0xf37c0c240000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002348656c6c6f2c20576f726c6421343536373839303132333435363738393031323334350000000000000000000000000000000000000000000000000000000000","nonce":10,"r":"0x7b6e3d23a11aece86ca6ef23ff5de36ebbe8545d5478a52b7331cbe4066d424","s":"0x3c16549ad5c508558801131ba0ee163dc8467dbbb67e54230ab81594b96c249d","to":"0xb4dbbb96c71853b12cbf87db17d1eb8028d54391","transactionIndex":0,"v":"0xa95","value":"0"}
modifyVars1TxReceipt={"blockHash":"0xed71e433960c495dd5dc840d1a6f0dc5a5fd8b1ccad6cf10b7be02915a0ee793","blockNumber":4657,"contractAddress":null,"cumulativeGasUsed":70258,"from":"0x223b07a19170754cf6414421c8c8633a47bb1a0d","gasUsed":70258,"logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0xb4dbbb96c71853b12cbf87db17d1eb8028d54391","transactionHash":"0x8865ef50938dfeee0b5a122f85be11476e0a31d4ccc7b71eb2ca64c3e09449d6","transactionIndex":0}
modifyVars2Tx={"blockHash":"0xed71e433960c495dd5dc840d1a6f0dc5a5fd8b1ccad6cf10b7be02915a0ee793","blockNumber":4657,"from":"0x223b07a19170754cf6414421c8c8633a47bb1a0d","gas":100000,"gasPrice":"1","hash":"0x77966d50a9c87e8a4a45142ee6cb89d87e7fa33a611765b4bc373c8ba06f8fda","input":"0x64d7a1e500000000000000000000000000000000000000000000000000000000000001c8","nonce":11,"r":"0xe42796d2ea7f61515e6e6009b7c666269d2199eb249c9ad4fef04a228a193a70","s":"0x1d3dbdd0870aa2ce11a3fe0eb991aa24235164c863b4424468d815216829ac84","to":"0xb4dbbb96c71853b12cbf87db17d1eb8028d54391","transactionIndex":1,"v":"0xa95","value":"0"}
modifyVars2TxReceipt={"blockHash":"0xed71e433960c495dd5dc840d1a6f0dc5a5fd8b1ccad6cf10b7be02915a0ee793","blockNumber":4657,"contractAddress":null,"cumulativeGasUsed":96947,"from":"0x223b07a19170754cf6414421c8c8633a47bb1a0d","gasUsed":26689,"logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0xb4dbbb96c71853b12cbf87db17d1eb8028d54391","transactionHash":"0x77966d50a9c87e8a4a45142ee6cb89d87e7fa33a611765b4bc373c8ba06f8fda","transactionIndex":1}
var1=Hello, World!4567890123456789012345
var2=456


Further Information

See BokkyPooBah's Token Teleportation Service Smart Contract/test/, in particular:

  • 00_runGeth.sh that includes the parameter --targetgaslimit 994712388 to increase the block gas limit
  • functions.js that includes a list of convenience functions such as unlockAccounts(...), printBalances(), printTxData(...), failIfTxStatusError(...), passIfTxStatusError(...), waitUntil(...), waitUntilBlock(...), printTokenContractDetails()
  • 01_test1.sh that includes the functions.js file and uses the convenience functions
  • test1output.txt that includes all the output from the geth attach command execution
  • test1results.txt that includes only the information from test1output.txt that I am interested in