-
Notifications
You must be signed in to change notification settings - Fork 13
Workshop #53 Raw JavaScript Smart Contracts Testing
Notes to go with #53 [Intermediate] Raw JavaScript Smart Contracts Testing & Continuation of #50.
- References
- Prerequisites
- Directories And Files
- Run
geth
in--dev
mode - Create
00_runGeth.sh
Script To Executegeth
- Create
01_runTest1.sh
To Execute The Test - Create
02_attach.sh
To Attach To Thegeth
Instance - Create A Sample
Workshop.sol
Smart Contract - Customise
01_runTest1.sh
To Only Display Data Of Interest - Customise
01_runTest1.sh
To Execute Your Contract Deployment And Tests - Further Information
- http://remix.ethereum.org
- https://tiswww.case.edu/php/chet/bash/bashref.html
- Workshop #31 [Introduction] Blockchains & Crypto Safety, And Smart Contracts #1
- https://ethereum.stackexchange.com/
- https://consensys.github.io/smart-contract-best-practices/
- https://github.com/ethereum/wiki/wiki/JavaScript-API
- https://www.howtogeek.com/249966/how-to-install-and-use-the-linux-bash-shell-on-windows-10/
- Operating system: Linux, OS/X or Linux on Windows
- Blockchain client: Go-Ethereum (
geth
) from releases and installation instructions - Solidity compiler: Solidity from releases and installation instructions
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
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 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 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 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 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.
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
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
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
Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd and other contributors - 2018. The MIT Licence.