Using
Hello World is an introduction to using very simple contracts with simpleth.
This document goes further. It shows many of the basic interactions with a contract as well as compiling a contract.
Test Contract
The examples will use the Test.sol
contract.
It was created for simpleth
unit and integration testing.
It has no purpose except to provide a variety of
transactions and variables for testing.
We’ll use it to show simpleth
usage.
Note
If you’d like to look at the contract while going through the examples:
The Natspec comments in the source file provide reference documentation for the contract in, Smart Contract Reference.
A copy of the source code is in the document, Test Contract.
The source file will be found in
<Python sys.prefix dir>/contracts/Test.sol
Deploy
simpleth.Contract.deploy()
will add a contract to the blockchain.
After a contract is deployed, it is ready for use.
1>>> from simpleth import Blockchain, Contract
2>>> b = Blockchain()
3>>> owner = b.address(0)
4>>> c = Contract('Test')
5>>> special_num = 42
6>>> receipt = c.deploy(owner, special_num)
7>>> c.address
8'0x6074DEA05C4B02A8afc21c1E06b22a7212217CFd'
Note
Line 2:
b
is oursimpleth.Blockchain
object for the examples.Line 3: Assign the blockchain account
simpleth.Blockchain.address()
for the first Ganache account toowner
.Line 4:
c
is oursimpleth.Contract
object for theTest
contract to be used in all examples.Line 6:
deploy
adds theTest
contract to the blockchain.Test
has a constructor parameter that we need provide with the arg,special_num
. This becomes the contract’s value for the state variable,initNum
.The contract’s constructor function sets the account address that sent the transaction as the
owner
of the contract. Since the Pythonowner
was used todeploy
, it becomes the Solidityowner
. This becomes important later. There are examples where only the Solidity contractowner
can run a transaction.deploy
returns the transaction receipt. To avoid having this print out, I stored it inreceipt
. We don’t need to do anything with it for now.Line 7: Gets the blockchain address where this contract now resides. It is the value of the
simpleth.Contract.address()
property. The address is shown on line 8.
Setup interpreter session
These Python statements are common to all the following examples. They are shown here and assumed to have been issued for the rest of the examples.
There is duplication of these statements and the Deploy example. In most cases, a contract is already deployed and you would start your Python session with the following statements.
1>>> from simpleth import Blockchain, Contract, Results, EventSearch, Convert
2>>> b = Blockchain()
3>>> owner = b.address(0)
4>>> user = b.address(1)
5>>> c = Contract('Test')
6>>> c.connect()
Important
Line 6: You must do a simpleth.Contract.connect()
before doing anything with a contract. A deploy
includes a
connect
; no need to do a connect after a deploy.
Note
See the Deploy
example above for comments relevant to lines
2, 3, and 5.
Line 4: Likewise, assign another account address to user
. This
will give us two accounts for our examples.
Get variables
simpleth.Contract.get_var()
will retrieve the
specified public state
variable.
1>>> c.get_var('initNum')
242
3>>> c.get_var('owner')
4'0xa894b8d26Cd25eCD3E154a860A86f7c75B12D993'
5>>> c.get_var('nums', 1)
61
7>>> c.get_var('nums', 2)
82
Note
Line 1 - Get the variable that was set by the
deploy
constructor arg.Line 3 - The address of the
owner
account.Line 5 -
nums
is an array of three unsigned ints.get_var
can not return a list, only a single value. Ask for the value of the second element by providing an arg with the index of 1. (Note: the contract defines the initial value ofnums
as [0,1,2]. There are transactions to change and use those values. We’ll get to those soon.)Line 7 - get the third element of
nums
.
Call functions
simpleth.Contract.call_fcn()
will execute a contract’s
public pure
or public view
functions and pass
back the returned value(s).
1>>> c.call_fcn('getNum0')
20
3>>> c.call_fcn('getNum',2)
42
5>>> c.call_fcn('getNums')
6[0, 1, 2]
7>>> c.call_fcn('getTypes')
8[True, 1, 10, -100, '0x20e0A619E7Efb741a34b8EDC6251E2702e69bBDd', 'test string', [10, 20, 30]]
Note
Line 1:
getNum0
returns one value: the int stored in nums[0].Line 3:
getNum
returns one value: the int stored in nums[<index>]. In this instance, we will get nums[2].Line 5:
getNums
returns the full nums array as a Python list.Line 7:
getTypes
returns seven values. (Note: I did a transaction to set these values that is not shown. We’ll see it soon.)
Run transactions
simpleth.Contract.run_trx()
will execute a contract’s
public
functions. run_trx()
is the typical and easiest
way to use transactions with Ganache.
Unlike a function, a transaction does not return any value. If you want to confirm a transaction, you might check for expected changes in contract state variables or for the emission of expected events.
Let’s run a few transactions and check the updated variable values:
1>>> receipt = c.run_trx(user, 'storeNum', 0, 1000)
2>>> c.get_var('nums', 0)
31000
4>>> receipt = c.run_trx(user, 'storeNums', 12, 34, 56)
5>>> c.get_var('nums', 0)
612
7>>> receipt = c.run_trx(owner, 'storeTypes', False, 2, 500, -500, c.address, 'new test string', [2, 4, 6])
8>>> c.get_var('testStr')
9'new test string'
Note
Line 1: The contract transaction,
storeNum
, sets nums[0] to 1000. After the transaction completes, line 2 gets the new value for nums[0], which is shown on line 3.Line 4: Set the three values in nums[] to 12, 34, 56.
Line 7: Runs a transaction,
storeTypes
, that shows how to pass in seven different data types as args. Line 9 confirms that the string arg was set properly.
The Solidity transaction does not return any value, but
call_fcn
will return the transaction receipt which
is created when the transaction is mined. You will be able
to use this in the upcoming section about Results to see
transaction information.
A transaction always has a sender. This is the address
of the account running the transaction. For the transactions
shown the sender does not matter. Two of them were sent by
user
and one owner
. We’ll be looking at checks in
a transaction that can restrict which account(s) are permitted
to run the transaction.
You can compare this approach to the upcoming examples of
Submit transactions
and Get transaction receipts
.
Search for events
simpleth.EventSearch
has two methods to find and retrieve
the information from events emitted by transactions:
simpleth.EventSearch.get_old()
returns event info from a specified range of previously mined blockssimpleth.EventSearch.get_new()
returns event info from newly mined blocks.
1>>> from simpleth import EventSearch
2>>> nums_stored_search = EventSearch(c, 'NumsStored')
3>>> events = nums_stored_search.get_old()
4>>> len(events)
51
6>>> events = nums_stored_search.get_old(-4)
7>>> len(events)
84
9>>> last_block = b.block_number
10>>> events = nums_stored_search.get_old(last_block-3, last_block)
11>>> len(events)
124
13>>> import pprint
14>>> pp = pprint.PrettyPrinter(indent=4)
15>>> pp.pprint(events)
16[ { 'args': {'num0': 10, 'num1': 10, 'num2': 10, 'timestamp': 1653095947},
17 'block_number': 7084,
18 'trx_hash': '0x38c917a6a5f27d88e4af57205f5a0ad231adcc5d519a2902feb7ab57885fe76a'},
19 { 'args': {'num0': 20, 'num1': 20, 'num2': 20, 'timestamp': 1653095957},
20 'block_number': 7085,
21 'trx_hash': '0xc9846c27b90f5c0744e4049e8e3ea54477157d0741692db84ded3d1fae7b638a'},
22 { 'args': {'num0': 30, 'num1': 30, 'num2': 30, 'timestamp': 1653095968},
23 'block_number': 7086,
24 'trx_hash': '0xed3ce6a50b8fb919c68c2555a8a525d3cf3b6e51ced660d28a7837961abfc385'},
25 { 'args': {'num0': 40, 'num1': 40, 'num2': 40, 'timestamp': 1653095980},
26 'block_number': 7087,
27 'trx_hash': '0x9a02a390381f1053cc73b8f9589624b3b38a63c49722a15acc8fed5296e0011c'}]
28>>> events[1]['args']
29{'timestamp': 1653095957, 'num0': 20, 'num1': 20, 'num2': 20}
30>>> events[1]['args']['num0']
3120
Note
Line 2: Create the event search object we’ll use to search for the event,
NumsStored
, which is emitted by the transaction,storeNums()
.Line 3: Without an arg
get_old()
looks in the last block on the chain for the event. Line 5 shows the block contains one such event.Line 6:
-4
asksget_old()
to look in the last four blocks on the chain. Line 8 shows that four events were found.Line 15: Print out the four events using Python’s pretty print. You can see the information stored when the
NumsStored
event is emitted.Line 28: Gets just the
args
values for the second event in the list.Line 30: Narrows it down getting the value for the
num0
parameter.
get_new()
is used to check for an event in recently mined blocks.
It will look in the blocks created since the previous call for any new events.
The checking starts with creating an EventSearch
object . The first call
to get_new
returns any events emitted since the object was created. The
next call returns any events emitted since the first call. The second call
returns events since the first call and so on.
1>>> nums_stored_search = EventSearch(c, 'NumsStored')
2>>> receipt = c.run_trx(user, 'storeNums', 50, 50, 50)
3>>> receipt = c.run_trx(user, 'storeNums', 60, 60, 60)
4>>> events = nums_stored_search.get_new()
5>>> len(events)
62
7>>> events = nums_stored_search.get_new()
8>>> len(events)
90
10>>> receipt = c.run_trx(user, 'storeNums', 70, 70, 70)
11>>> events = nums_stored_search.get_new()
12>>> len(events)
131
14>>> pp.pprint(events)
15[ { 'args': {'num0': 70, 'num1': 70, 'num2': 70, 'timestamp': 1653097033},
16 'block_number': 7090,
17 'trx_hash': '0x5b60aafd384ec3cbfb86f28cc79911a8265899d0b38335cceb482f9cf9be9830'}]
Note
Line 1: Create the
EventSearch
object. This marks that stating point of checking for newNumsStored
events.Line 2: Run two transactions to emit two events.
Line 4: Check for new events. Two are found, as expected.
Line 7: Check for new events since that last check (on line 4). None found, as expected.
Line 10: Run one transaction, get it on line 11, and print it on line 14.
There is no way to be alerted to a new event without checking periodically. There is no callback nor pub/sub available. A simple approach is to have a program that checks for the event, sleeps for a period of time, and repeats. Here’s an example:
1"""Simple program to periodically check for an event"""
2
3import time
4from simpleth import Contract, EventSearch
5
6poll_freq = 3 # number of seconds between checks
7num_polls = 10 # number of checks
8contract_name = 'TEST' # contract emitting event
9event_name = 'NumsStore' # check for this event
10
11c = Contract('Test')
12c.connect()
13e = EventSearch(c, 'NumsStored')
14
15while num_polls > 0:
16 events = e.get_new()
17 num_events = len(events)
18 if num_events:
19 print(f'Found {num_events} new events')
20 else:
21 print(f'No new events')
22 num_polls = num_polls - 1
23 time.sleep(poll_freq)
Note
Line 6: This program will check every three seconds
Line 7: Ten of these checks will be done before the program ends.
Line 16: Highlighted. Here is the periodic poll to check for any recent events.
Line 17: If zero events, tell the user nothing new found. If non-zero, tell user how many we found in this polling cycle.
Line 23: Sleep until time for the next check.
The program is found in <Python sys.prefix>/examples
directory.
The next two sessions show a single test of event_poll.py
.
There are two windows in use:
Python interpreter where transactions were run
Command line window where
event_poll.py
runs.
I started event_poll.py
and then switched to the Python interpreter
to run eight identical storeNums()
transactions at random
intervals.
The transactions:
1>>> receipt = c.run_trx(user, 'storeNums', 500, 500, 500)
2>>> receipt = c.run_trx(user, 'storeNums', 500, 500, 500)
3>>> receipt = c.run_trx(user, 'storeNums', 500, 500, 500)
4>>> receipt = c.run_trx(user, 'storeNums', 500, 500, 500)
5>>> receipt = c.run_trx(user, 'storeNums', 500, 500, 500)
6>>> receipt = c.run_trx(user, 'storeNums', 500, 500, 500)
7>>> receipt = c.run_trx(user, 'storeNums', 500, 500, 500)
8>>> receipt = c.run_trx(user, 'storeNums', 500, 500, 500)
The program:
1$ event_poll.py
2No new events
3No new events
4Found 2 new events
5No new events
6Found 3 new events
7Found 1 new events
8No new events
9No new events
10Found 2 new events
11No new events
Note
Line 2: No events emitted in the first 3 seconds.
Line 3: No events emitted in the next 3 seconds.
Line 4: Two events, from the transactions run in the Python interpreter, were emitted in the third 3 seconds.
And so on.
After event_poll.py
finished, use get_old()
to get the
eight events emitted. Print them.
1>>> events = e.get_old(-8)
2>>> len(events)
38
4>>> pp.pprint(events)
5[ { 'args': {'num0': 500, 'num1': 500, 'num2': 500, 'timestamp': 1653135341},
6 'block_number': 7125,
7 'trx_hash': '0xc258c1f566fbf9b76253afc2d89049fb7f7d7fe54f5c6b5a98a521f5bb0e9bc0'},
8 { 'args': {'num0': 500, 'num1': 500, 'num2': 500, 'timestamp': 1653135341},
9 'block_number': 7126,
10 'trx_hash': '0x7f06283aa8c2326f558da4ea36d1d840fd198a92874ae587164b8950d9dd7259'},
11 { 'args': {'num0': 500, 'num1': 500, 'num2': 500, 'timestamp': 1653135347},
12 'block_number': 7127,
13 'trx_hash': '0xcdf32bafe94c90f10ef93a4ed989a4f41f022ef62299076be549a713517a9667'},
14 { 'args': {'num0': 500, 'num1': 500, 'num2': 500, 'timestamp': 1653135347},
15 'block_number': 7128,
16 'trx_hash': '0xa4c1fdaa89120cdf69ecc42300d6594098e90a443b5fdbda8bed91b355dcde8f'},
17 { 'args': {'num0': 500, 'num1': 500, 'num2': 500, 'timestamp': 1653135348},
18 'block_number': 7129,
19 'trx_hash': '0x3763fbaf62eb8e422f33f41fc42607559a478152cbf10c437c5178381e8905ff'},
20 { 'args': {'num0': 500, 'num1': 500, 'num2': 500, 'timestamp': 1653135349},
21 'block_number': 7130,
22 'trx_hash': '0xbfb52e30129dcf927a2ff07d426210302bea48e9f54c8c88a5a29b6b474bbfe0'},
23 { 'args': {'num0': 500, 'num1': 500, 'num2': 500, 'timestamp': 1653135360},
24 'block_number': 7131,
25 'trx_hash': '0x18155d00b5305d15959536f107af1b533a877ee324989da475fb8e4744c888b3'},
26 { 'args': {'num0': 500, 'num1': 500, 'num2': 500, 'timestamp': 1653135360},
27 'block_number': 7132,
28 'trx_hash': '0x14e74f83b9c544675cee2718212b31563914f81c5124d5df024b6b4bef8e7b7f'}]
Note
Line 1: Eight transactions were run in the Python interpreter. Get events in the most recent eight blocks. We do not need to create another
EventSearch
object. We use the same one used forget_new
.Line 3: shows eight events emitted. This matches the number that
event_poll.py
found.Line 5: The events list has the eight events. You can see the (epoch) times, in seconds, when the transactions were mined in the
timestamp
args. The first two events have the same timestamp. This corresponds toevent_poll.py
finding two events in the third three-second check. The next three events were timestamped in a two-second period. They were found byevent_poll.py
in the fifth three-second check.
And so on.
Search for events with event arguments
simpleth.EventSearch
has an optional parameter to specify
event_args
. This allows you to narrow the search to events with
a desired value for an event parameter.
You setup and call either simpleth.EventSearch.get_old()
or
simpleth.EventSearch.get_new()
as above. But, they will only
return events where the the event argument and its value match
the event_args
you specified in simpleth.EventSearch
.
You can specify multiple args and values in the event_args
dictionary.
These will be ANDed together. Your search will return only events that
meet all the criteria. You should specify the name of an event argument
only once. If the dictionary repeats a key, only the last one is used.
1 >>> import pprint
2 >>> pp = pprint.PrettyPrinter(indent=4)
3 >>> from simpleth import EventSearch, Contract
4 >>> c = Contract('Test')
5 >>> c.connect()
6 >>> all_nums_stored = EventSearch(c, 'NumsStored')
7 >>> pp.pprint(all_nums_stored.get_old(-5))
8 [ { 'args': {'num0': 10, 'num1': 10, 'num2': 10, 'timestamp': 1659807711},
9 'block_number': 6940,
10 'trx_hash': '0xac4da74d96c3854b276c138e9b1984638f1d78d0c0e739973bd669e6cde0de47'},
11 { 'args': {'num0': 10, 'num1': 10, 'num2': 20, 'timestamp': 1659807729},
12 'block_number': 6941,
13 'trx_hash': '0xba5c070b1e39de9520c3f75bef2a3e85d9070d967d5235c427d4c3104125bf5a'},
14 { 'args': {'num0': 10, 'num1': 20, 'num2': 20, 'timestamp': 1659807737},
15 'block_number': 6942,
16 'trx_hash': '0x5158aeb329c780da6bc508ae43cad8cf83a114dd07cd44b101a48b0dcaf246af'},
17 { 'args': {'num0': 20, 'num1': 20, 'num2': 20, 'timestamp': 1659807752},
18 'block_number': 6943,
19 'trx_hash': '0x5d573d5d636b8ebad2d1d9d0e767ff51547e050220b4e53cef54bb5220707b51'}]
20 >>> num0_is_10 = EventSearch(c, 'NumsStored', {'num0': 10})
21 >>> pp.pprint(num0_is_10.get_old(-5))
22 [ { 'args': {'num0': 10, 'num1': 10, 'num2': 10, 'timestamp': 1659807711},
23 'block_number': 6940,
24 'trx_hash': '0xac4da74d96c3854b276c138e9b1984638f1d78d0c0e739973bd669e6cde0de47'},
25 { 'args': {'num0': 10, 'num1': 10, 'num2': 20, 'timestamp': 1659807729},
26 'block_number': 6941,
27 'trx_hash': '0xba5c070b1e39de9520c3f75bef2a3e85d9070d967d5235c427d4c3104125bf5a'},
28 { 'args': {'num0': 10, 'num1': 20, 'num2': 20, 'timestamp': 1659807737},
29 'block_number': 6942,
30 'trx_hash': '0x5158aeb329c780da6bc508ae43cad8cf83a114dd07cd44b101a48b0dcaf246af'}]
31 >>> pp.pprint(num0_is_10.get_old(from_block=6942))
32 [ { 'args': {'num0': 10, 'num1': 20, 'num2': 20, 'timestamp': 1659807737},
33 'block_number': 6942,
34 'trx_hash': '0x5158aeb329c780da6bc508ae43cad8cf83a114dd07cd44b101a48b0dcaf246af'}]
35 >>> num0_is_10_and_num1_is_10 = EventSearch(c, 'NumsStored', {'num0': 10, 'num1':10})
36 >>> pp.pprint(num0_is_10_and_num1_is_10.get_old(-5))
37 [ { 'args': {'num0': 10, 'num1': 10, 'num2': 10, 'timestamp': 1659807711},
38 'block_number': 6940,
39 'trx_hash': '0xac4da74d96c3854b276c138e9b1984638f1d78d0c0e739973bd669e6cde0de47'},
40 { 'args': {'num0': 10, 'num1': 10, 'num2': 20, 'timestamp': 1659807729},
41 'block_number': 6941,
42 'trx_hash': '0xba5c070b1e39de9520c3f75bef2a3e85d9070d967d5235c427d4c3104125bf5a'}]
43 >>> all_nums_are_20 = EventSearch(c, 'NumsStored', {'num0': 10, 'num1':10, 'num2':10})
44 >>> pp.pprint(all_nums_are_20.get_old(-5))
45 [ { 'args': {'num0': 10, 'num1': 10, 'num2': 10, 'timestamp': 1659807711},
46 'block_number': 6940,
47 'trx_hash': '0xac4da74d96c3854b276c138e9b1984638f1d78d0c0e739973bd669e6cde0de47'}]
48 >>>
49 >>> num0_is_10.get_new()
50 []
51 >>> r = c.run_trx(u, 'storeNums', 10, 100, 1000)
52 >>> pp.pprint(num0_is_10.get_new())
53 [ { 'args': { 'num0': 10,
54 'num1': 100,
55 'num2': 1000,
56 'timestamp': 1659808773},
57 'block_number': 6944,
58 'trx_hash': '0x00965e2e84c9b6940ac3129bc1f2a97a720b7b56085e029ad1828a7afc1cb0d3'}]
Note
Line 6: Create an event search to find all NumsStored.
Line 7: Get all ‘NumsStored’ events in last five mined blocks and pretty print them
Line 20: Create a second event search to find events where ‘num0’ was set to 10.
Line 21: Finds three of those in the last five blocks.
Line 31: Shows how to check if block number 6942 has a transaction that emitted ‘NumsStored’ with num0 equal to 10. One is found.
Line 35: Shows how to specify multiple arguments/values. Here we want to find StoredNum events have both num1 and num2 equal to 10. There are two in the last five blocks mined.
Line 43: Go a step further and look for events where all three numbers were set to 10. There is one found.
Line 49: Switch to showing how
get_new()
operates. We can look for any newly mined transactions where num0 is 10. Line 51 shows that there have been none since num0_is_10 EventSearch was defined back on line 20.Line 51: Run a transaction that has num0 equal to 10. (Defining the sender
u
is not shown.)Line 52: As expected. when we check for a new transaction it is returned.
Transaction results
simpleth.Results
can be used after a transaction completes
to see the details about it.
1>>> from simpleth import Results
2>>> receipt = c.run_trx(user, 'storeNums', 42, 42, 42)
3>>> r = Results(c, receipt)
4>>> r.block_number
57238
6>>> r.gas_used
738764
8>>> r.gas_price_wei
920000000000
10>>> pp.pprint(r.transaction)
11{ 'blockHash': '0x02d037b430ff01bec0395f63af90c9f497d31ff5f2270bd1410056f54d166db0',
12 'blockNumber': 7238,
13 'from': '0x20e0A619E7Efb741a34b8EDC6251E2702e69bBDd',
14 'gas': 6000000,
15 'gasPrice': 20000000000,
16 'hash': '0xf73105578c2df584331431703b07fb4741fd1292d890febfc77ded9f4dfd0e91',
17 'input': '0x3e50ca2c000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002a',
18 'nonce': 209,
19 'r': '0xdd4bd76385c7c3d5775db03951c03b3c529383288f036baca55a05f8c5088d54',
20 's': '0x21c27b449376503812586b3ddf9edeb40a6e920b5f1f019d8f9f54243d2e29ad',
21 'to': '0x82592d5ae9E9ECc14b1740F330D3fAA00403a1F3',
22 'transactionIndex': 0,
23 'v': 37,
24 'value': 0}
25>>> print(r)
26 Block number = 7238
27 Block time epoch = 1653156539
28 Contract name = Test
29 Contract address = 0x82592d5ae9E9ECc14b1740F330D3fAA00403a1F3
30 Trx name = storeNums
31 Trx args = {'_num0': 42, '_num1': 42, '_num2': 42}
32 Trx sender = 0x20e0A619E7Efb741a34b8EDC6251E2702e69bBDd
33 Trx value wei = 0
34 Trx hash = 0xf73105578c2df584331431703b07fb4741fd1292d890febfc77ded9f4dfd0e91
35 Gas price wei = 20000000000
36 Gas used = 38764
37 Event name[0] = NumsStored
38 Event args[0] = {'timestamp': 1653156539, 'num0': 42, 'num1': 42, 'num2': 42}
Note
Line 3: Create a
Results
data object,r
, for thestoreNums
transaction.Line 4: Get blockchain block number holding this mined transaction.
Line 6: Get the units of gas consumed to execute the transaction.
Line 8: Get the cost, in wei , for each unit of gas. This is a constant when using Ganache.
Line 10: Pretty print the
web3.eth
transaction information.Line 25: A
Results
object can be printed. Here’s the output.
See simpleth.Results
documentation for the full list of
properties, including more from web3.eth
.
Handling Ether
simpleth
has a handful of methods and properties for handling Ether:
simpleth.Convert.denominations_to_wei()
returns Ether denominations and values.simpleth.Convert.convert_ether()
to convert amount from one denomination to another.simpleth.Blockchain.balance_of()
returns the Ether balance_of, in wei , for a specified address.simpleth.Blockchain.send_ether()
transfers the specified amount of Ether, in wei , from one address to another.simpleth.Contract.run_trx()
has an optional parameter,value_wei
which will send the specified amount of Ether, in wei , to the transaction.
1 >>> from simpleth import Convert
2 >>> v = Convert()
3 >>> v.denominations_to_wei()['szabo']
4 1000000000000
5
6 >>> int(v.convert_ether(20, 'ether', 'gwei'))
7 20000000000
8 >>> float(v.convert_ether(100, 'wei', 'ether'))
9 1e-16
10
11 >>> b.balance(owner)
12 57816514559996298520
13 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
14 99.52299804
15 >>> b.balance(c.address)
16 10
17
18 >>> b.balance_of(user)
19 99522998040000000000
20 >>> trx_hash = b.send_ether(owner, user, 10)
21 >>> b.balance(user)
22 99522998040000000010
23
24 >>> b.balance(c.address)
25 10
26 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
27 >>> Results(c, receipt).trx_value_wei
28 100
29 >>> b.balance(c.address)
30 110
31 >>> b.send_ether(user, c.address, 500)
32 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
33 >>> b.balance(c.address)
34 610
35
36 >>> from simpleth import Convert
37 >>> v = Convert()
38 >>> v.denominations_to_wei()['szabo']
39 1000000000000
40
41 >>> int(v.convert_ether(20, 'ether', 'gwei'))
42 20000000000
43 >>> float(v.convert_ether(100, 'wei', 'ether'))
44 1e-16
45
46 >>> b.balance(owner)
47 57816514559996298520
48 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
49 99.52299804
50 >>> b.balance(c.address)
51 10
52
53 >>> b.balance_of(user)
54 99522998040000000000
55 >>> trx_hash = b.send_ether(owner, user, 10)
56 >>> b.balance(user)
57 99522998040000000010
58
59 >>> b.balance(c.address)
60 10
61 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
62 >>> Results(c, receipt).trx_value_wei
63 100
64 >>> b.balance(c.address)
65 110
66 >>> b.send_ether(user, c.address, 500)
67 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
68 >>> b.balance(c.address)
69 610
70
71 >>> from simpleth import Convert
72 >>> v = Convert()
73 >>> v.denominations_to_wei()['szabo']
74 1000000000000
75
76 >>> int(v.convert_ether(20, 'ether', 'gwei'))
77 20000000000
78 >>> float(v.convert_ether(100, 'wei', 'ether'))
79 1e-16
80
81 >>> b.balance(owner)
82 57816514559996298520
83 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
84 99.52299804
85 >>> b.balance(c.address)
86 10
87
88 >>> b.balance(user)
89 99522998040000000000
90 >>> trx_hash = b.send_ether(owner, user, 10)
91 >>> b.balance(user)
92 99522998040000000010
93
94 >>> b.balance_of(c.address)
95 10
96 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
97 >>> Results(c, receipt).trx_value_wei
98 100
99 >>> b.balance(c.address)
100 110
101 >>> b.send_ether(user, c.address, 500)
102 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
103 >>> b.balance(c.address)
104 610
105
106 >>> from simpleth import Convert
107 >>> v = Convert()
108 >>> v.denominations_to_wei()['szabo']
109 1000000000000
110
111 >>> int(v.convert_ether(20, 'ether', 'gwei'))
112 20000000000
113 >>> float(v.convert_ether(100, 'wei', 'ether'))
114 1e-16
115
116 >>> b.balance(owner)
117 57816514559996298520
118 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
119 99.52299804
120 >>> b.balance(c.address)
121 10
122
123 >>> b.balance(user)
124 99522998040000000000
125 >>> trx_hash = b.send_ether(owner, user, 10)
126 >>> b.balance(user)
127 99522998040000000010
128
129 >>> b.balance_of(c.address)
130 10
131 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
132 >>> Results(c, receipt).trx_value_wei
133 100
134 >>> b.balance(c.address)
135 110
136 >>> b.send_ether(user, c.address, 500)
137 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
138 >>> b.balance(c.address)
139 610
140
141 >>> from simpleth import Convert
142 >>> v = Convert()
143 >>> v.denominations_to_wei()['szabo']
144 1000000000000
145
146 >>> int(v.convert_ether(20, 'ether', 'gwei'))
147 20000000000
148 >>> float(v.convert_ether(100, 'wei', 'ether'))
149 1e-16
150
151 >>> b.balance(owner)
152 57816514559996298520
153 >>> float(v.convert_ether(b.balance_of(user), 'wei', 'ether'))
154 99.52299804
155 >>> b.balance(c.address)
156 10
157
158 >>> b.balance(user)
159 99522998040000000000
160 >>> trx_hash = b.send_ether(owner, user, 10)
161 >>> b.balance(user)
162 99522998040000000010
163
164 >>> b.balance(c.address)
165 10
166 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
167 >>> Results(c, receipt).trx_value_wei
168 100
169 >>> b.balance(c.address)
170 110
171 >>> b.send_ether(user, c.address, 500)
172 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
173 >>> b.balance(c.address)
174 610
175
176 >>> from simpleth import Convert
177 >>> v = Convert()
178 >>> v.denominations_to_wei()['szabo']
179 1000000000000
180
181 >>> int(v.convert_ether(20, 'ether', 'gwei'))
182 20000000000
183 >>> float(v.convert_ether(100, 'wei', 'ether'))
184 1e-16
185
186 >>> b.balance(owner)
187 57816514559996298520
188 >>> float(v.convert_ether(b.balance_of(user), 'wei', 'ether'))
189 99.52299804
190 >>> b.balance(c.address)
191 10
192
193 >>> b.balance(user)
194 99522998040000000000
195 >>> trx_hash = b.send_ether(owner, user, 10)
196 >>> b.balance(user)
197 99522998040000000010
198
199 >>> b.balance(c.address)
200 10
201 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
202 >>> Results(c, receipt).trx_value_wei
203 100
204 >>> b.balance(c.address)
205 110
206 >>> b.send_ether(user, c.address, 500)
207 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
208 >>> b.balance(c.address)
209 610
210
211 >>> from simpleth import Convert
212 >>> v = Convert()
213 >>> v.denominations_to_wei()['szabo']
214 1000000000000
215
216 >>> int(v.convert_ether(20, 'ether', 'gwei'))
217 20000000000
218 >>> float(v.convert_ether(100, 'wei', 'ether'))
219 1e-16
220
221 >>> b.balance_of(owner)
222 57816514559996298520
223 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
224 99.52299804
225 >>> b.balance(c.address)
226 10
227
228 >>> b.balance(user)
229 99522998040000000000
230 >>> trx_hash = b.send_ether(owner, user, 10)
231 >>> b.balance(user)
232 99522998040000000010
233
234 >>> b.balance(c.address)
235 10
236 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
237 >>> Results(c, receipt).trx_value_wei
238 100
239 >>> b.balance(c.address)
240 110
241 >>> b.send_ether(user, c.address, 500)
242 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
243 >>> b.balance(c.address)
244 610
245
246 >>> from simpleth import Convert
247 >>> v = Convert()
248 >>> v.denominations_to_wei()['szabo']
249 1000000000000
250
251 >>> int(v.convert_ether(20, 'ether', 'gwei'))
252 20000000000
253 >>> float(v.convert_ether(100, 'wei', 'ether'))
254 1e-16
255
256 >>> b.balance_of(owner)
257 57816514559996298520
258 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
259 99.52299804
260 >>> b.balance(c.address)
261 10
262
263 >>> b.balance(user)
264 99522998040000000000
265 >>> trx_hash = b.send_ether(owner, user, 10)
266 >>> b.balance(user)
267 99522998040000000010
268
269 >>> b.balance(c.address)
270 10
271 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
272 >>> Results(c, receipt).trx_value_wei
273 100
274 >>> b.balance(c.address)
275 110
276 >>> b.send_ether(user, c.address, 500)
277 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
278 >>> b.balance(c.address)
279 610
280
281 >>> from simpleth import Convert
282 >>> v = Convert()
283 >>> v.denominations_to_wei()['szabo']
284 1000000000000
285
286 >>> int(v.convert_ether(20, 'ether', 'gwei'))
287 20000000000
288 >>> float(v.convert_ether(100, 'wei', 'ether'))
289 1e-16
290
291 >>> b.balance(owner)
292 57816514559996298520
293 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
294 99.52299804
295 >>> b.balance_of(c.address)
296 10
297
298 >>> b.balance(user)
299 99522998040000000000
300 >>> trx_hash = b.send_ether(owner, user, 10)
301 >>> b.balance(user)
302 99522998040000000010
303
304 >>> b.balance(c.address)
305 10
306 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
307 >>> Results(c, receipt).trx_value_wei
308 100
309 >>> b.balance(c.address)
310 110
311 >>> b.send_ether(user, c.address, 500)
312 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
313 >>> b.balance(c.address)
314 610
315
316 >>> from simpleth import Convert
317 >>> v = Convert()
318 >>> v.denominations_to_wei()['szabo']
319 1000000000000
320
321 >>> int(v.convert_ether(20, 'ether', 'gwei'))
322 20000000000
323 >>> float(v.convert_ether(100, 'wei', 'ether'))
324 1e-16
325
326 >>> b.balance(owner)
327 57816514559996298520
328 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
329 99.52299804
330 >>> b.balance_of(c.address)
331 10
332
333 >>> b.balance(user)
334 99522998040000000000
335 >>> trx_hash = b.send_ether(owner, user, 10)
336 >>> b.balance(user)
337 99522998040000000010
338
339 >>> b.balance(c.address)
340 10
341 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
342 >>> Results(c, receipt).trx_value_wei
343 100
344 >>> b.balance(c.address)
345 110
346 >>> b.send_ether(user, c.address, 500)
347 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
348 >>> b.balance(c.address)
349 610
350
351 >>> from simpleth import Convert
352 >>> v = Convert()
353 >>> v.denominations_to_wei()['szabo']
354 1000000000000
355
356 >>> int(v.convert_ether(20, 'ether', 'gwei'))
357 20000000000
358 >>> float(v.convert_ether(100, 'wei', 'ether'))
359 1e-16
360
361 >>> b.balance(owner)
362 57816514559996298520
363 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
364 99.52299804
365 >>> b.balance(c.address)
366 10
367
368 >>> b.balance(user)
369 99522998040000000000
370 >>> trx_hash = b.send_ether(owner, user, 10)
371 >>> b.balance(user)
372 99522998040000000010
373
374 >>> b.balance(c.address)
375 10
376 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
377 >>> Results(c, receipt).trx_value_wei
378 100
379 >>> b.balance_of(c.address)
380 110
381 >>> b.send_ether(user, c.address, 500)
382 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
383 >>> b.balance(c.address)
384 610
385
386 >>> from simpleth import Convert
387 >>> v = Convert()
388 >>> v.denominations_to_wei()['szabo']
389 1000000000000
390
391 >>> int(v.convert_ether(20, 'ether', 'gwei'))
392 20000000000
393 >>> float(v.convert_ether(100, 'wei', 'ether'))
394 1e-16
395
396 >>> b.balance(owner)
397 57816514559996298520
398 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
399 99.52299804
400 >>> b.balance(c.address)
401 10
402
403 >>> b.balance(user)
404 99522998040000000000
405 >>> trx_hash = b.send_ether(owner, user, 10)
406 >>> b.balance(user)
407 99522998040000000010
408
409 >>> b.balance(c.address)
410 10
411 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
412 >>> Results(c, receipt).trx_value_wei
413 100
414 >>> b.balance_of(c.address)
415 110
416 >>> b.send_ether(user, c.address, 500)
417 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
418 >>> b.balance(c.address)
419 610
420
421 >>> from simpleth import Convert
422 >>> v = Convert()
423 >>> v.denominations_to_wei()['szabo']
424 1000000000000
425
426 >>> int(v.convert_ether(20, 'ether', 'gwei'))
427 20000000000
428 >>> float(v.convert_ether(100, 'wei', 'ether'))
429 1e-16
430
431 >>> b.balance(owner)
432 57816514559996298520
433 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
434 99.52299804
435 >>> b.balance(c.address)
436 10
437
438 >>> b.balance(user)
439 99522998040000000000
440 >>> trx_hash = b.send_ether(owner, user, 10)
441 >>> b.balance_of(user)
442 99522998040000000010
443
444 >>> b.balance(c.address)
445 10
446 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
447 >>> Results(c, receipt).trx_value_wei
448 100
449 >>> b.balance(c.address)
450 110
451 >>> b.send_ether(user, c.address, 500)
452 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
453 >>> b.balance(c.address)
454 610
455
456 >>> from simpleth import Convert
457 >>> v = Convert()
458 >>> v.denominations_to_wei()['szabo']
459 1000000000000
460
461 >>> int(v.convert_ether(20, 'ether', 'gwei'))
462 20000000000
463 >>> float(v.convert_ether(100, 'wei', 'ether'))
464 1e-16
465
466 >>> b.balance(owner)
467 57816514559996298520
468 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
469 99.52299804
470 >>> b.balance(c.address)
471 10
472
473 >>> b.balance(user)
474 99522998040000000000
475 >>> trx_hash = b.send_ether(owner, user, 10)
476 >>> b.balance_of(user)
477 99522998040000000010
478
479 >>> b.balance(c.address)
480 10
481 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
482 >>> Results(c, receipt).trx_value_wei
483 100
484 >>> b.balance(c.address)
485 110
486 >>> b.send_ether(user, c.address, 500)
487 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
488 >>> b.balance(c.address)
489 610
490
491 >>> from simpleth import Convert
492 >>> v = Convert()
493 >>> v.denominations_to_wei()['szabo']
494 1000000000000
495
496 >>> int(v.convert_ether(20, 'ether', 'gwei'))
497 20000000000
498 >>> float(v.convert_ether(100, 'wei', 'ether'))
499 1e-16
500
501 >>> b.balance(owner)
502 57816514559996298520
503 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
504 99.52299804
505 >>> b.balance(c.address)
506 10
507
508 >>> b.balance(user)
509 99522998040000000000
510 >>> trx_hash = b.send_ether(owner, user, 10)
511 >>> b.balance(user)
512 99522998040000000010
513
514 >>> b.balance(c.address)
515 10
516 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
517 >>> Results(c, receipt).trx_value_wei
518 100
519 >>> b.balance(c.address)
520 110
521 >>> b.send_ether(user, c.address, 500)
522 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
523 >>> b.balance_of(c.address)
524 610
525
526 >>> from simpleth import Convert
527 >>> v = Convert()
528 >>> v.denominations_to_wei()['szabo']
529 1000000000000
530
531 >>> int(v.convert_ether(20, 'ether', 'gwei'))
532 20000000000
533 >>> float(v.convert_ether(100, 'wei', 'ether'))
534 1e-16
535
536 >>> b.balance(owner)
537 57816514559996298520
538 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
539 99.52299804
540 >>> b.balance(c.address)
541 10
542
543 >>> b.balance(user)
544 99522998040000000000
545 >>> trx_hash = b.send_ether(owner, user, 10)
546 >>> b.balance(user)
547 99522998040000000010
548
549 >>> b.balance(c.address)
550 10
551 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
552 >>> Results(c, receipt).trx_value_wei
553 100
554 >>> b.balance(c.address)
555 110
556 >>> b.send_ether(user, c.address, 500)
557 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
558 >>> b.balance_of(c.address)
559 610
560
561 >>> from simpleth import Convert
562 >>> v = Convert()
563 >>> v.denominations_to_wei()['szabo']
564 1000000000000
565
566 >>> int(v.convert_ether(20, 'ether', 'gwei'))
567 20000000000
568 >>> float(v.convert_ether(100, 'wei', 'ether'))
569 1e-16
570
571 >>> b.balance(owner)
572 57816514559996298520
573 >>> float(v.convert_ether(b.balance(user), 'wei', 'ether'))
574 99.52299804
575 >>> b.balance(c.address)
576 10
577
578 >>> b.balance(user)
579 99522998040000000000
580 >>> trx_hash = b.send_ether(owner, user, 10)
581 >>> b.balance(user)
582 99522998040000000010
583
584 >>> b.balance(c.address)
585 10
586 >>> receipt = c.run_trx(user, 'storeNumsAndPay', 10, 20, 30, value_wei=100)
587 >>> Results(c, receipt).trx_value_wei
588 100
589 >>> b.balance(c.address)
590 110
591 >>> b.send_ether(user, c.address, 500)
592 '0xcbbec5f820b25318d5654526d7390ba6d74231d194775304a7cddfc3b075a652'
593 >>> b.balance(c.address)
594 610
Note
Line 23: You can specify a denomination to get the value in wei. See the the Example for
simpleth.Convert.denominations_to_wei()
for the list of valid denominations.Line 6:
convert_ether()
is the usual way to compute a conversion between denominations. This line shows the number of gwei in 20 ether. For best precision, the method returns adecimal
type. This example casts to an integer.Line 13: Get user balance in ether.
Line 15:
Test
contract has a balance of 10 wei.Line 20: Move 10 wei from
owner
touser
.Line 24:
user
balance increased by 10 wei. Line 43 is the before balance.Line 26: Example of sending ether to a transaction. The
Test
contract has the function,storeNumsAndPay()
that is identical to our trusty,storeNums()
, except it is defined aspayable
in the contract. This allows us to send Ether when we run the transaction. Here, we are sending 10 wei .Line 27: Get the
trx_value_wei()
sent to the transaction. As expected, line 52 shows it is 100 wei.Line 30: Confirms that 100 wei were sent. The balance is now 100 wei more than the before balance on line 49
Line 31: You can also send ether to a contract. Here, 500 wei is sent to the
Test
contract. This is confirmed in line 58 where the balance increased by 500 from the before balance on line 54. Important: the contract must have apayable
fallback function in order to receive ether. TheTest
contract has such a function as the final function in the contract.
Handling bytes
Passing values for transaction arguments with the Solidity data type of bytes requires an understanding of how a smart contract stores and returns those values plus Python functions to create and use the values.
We will be using the bytes variables along with their getter, setter, and event in Test.sol. This code allows testing of two fixed-size arrays of bytes, one of four-bytes and the other of 32-bytes, along with one dynamic array of bytes.
Solidity uses big endian format for storing bytes and characters are encoded using the unicode standard, utf-8. This will influence parameters used in a few of the Python functions.
1 bytes4 public testBytes4;
2 bytes32 public testBytes32;
3 bytes public testBytes;
4
5 function getBytes()
6 public
7 view
8 returns(
9 bytes4 testBytes4_,
10 bytes32 testBytes32_,
11 bytes memory testBytes_
12 )
13 {
14 return (testBytes4, testBytes32, testBytes);
15 }
16
17 function storeBytes(
18 bytes4 _testBytes4,
19 bytes32 _testBytes32,
20 bytes memory _testBytes
21 )
22 public
23 {
24 testBytes4 = _testBytes4;
25 testBytes32 = _testBytes32;
26 testBytes = _testBytes;
27 emit BytesStored(
28 block.timestamp,
29 testBytes4,
30 testBytes32,
31 testBytes
32 );
33 }
Initial null value
The three bytes public state variables are not given an initial value in the contract. Use getBytes() to return them. The bytes4 variable is set to four null bytes. The bytes32 vairable is set to 32 null bytes. The bytes variable is set to an empty array. Python shows them as byte strings.
1 >>> from simpleth import Contract, Blockchain
2 >>> t = Contract('Test')
3 >>> t.connect()
4 '0x16db9563B047A1535629389ED5AA4a3494B753a7'
5 >>> t.call_fcn('getBytes')
6 [b'\x00\x00\x00\x00', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'']
7 >>>
Set a character value
The example below will store the characters, ‘a’, ‘b’, and ‘c’ as three bytes in the four-byte, 32-byte, and byte array variables. Python’s method, encode(), will create the array of bytes that is passed as args to storeBytes(). encode() uses utf-8 as the default encoding.
The call to getBytes() returns the three Solidiity variable values. The two fixed byte array variables are null-padded to fill out the length of the byte array. These are the bytes with values of x00. The dynamic byte array variable does not have any padding.
To convert the Python byte arrays to a string, use the decode() method. The resulting string will have the null padding at the end. One approach, using split(), is shown to remove those nulls.
1 >>> b = Blockchain()
2 >>> user = b.address(4)
3 >>> string = 'abc'
4 >>> string_as_bytes = string.encode()
5 >>> trx_receipt = t.run_trx(user, 'storeBytes', string_as_bytes, string_as_bytes, string_as_bytes)
6 >>> values = t.call_fcn('getBytes')
7 >>> values
8 [b'abc\x00', b'abc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'abc']
9 >>> values[1].decode()
10 'abc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
11 >>> values[1].decode().split('\x00',1)[0]
12 'abc'
Set an integer value
If you need to store an integer value in a bytes data, use Python’s to_bytes() method to create the byte string and pass that as an argument to Solidity. You must specify the number of bytes to hold the integer. Null bytes will be prepended to fill out the fixed byte array. For a dynamic byte array, if you wish to have the smallest array size, specify the number of bytes that will hold the binary value for the integer. You will also specify big endian format.
The example below creates three different values to use as the args for storeBytes(), each is a different size. After storing the bytes, the value for the public state variable, testBytes4 is shown. Next a call to getBytes() returns the values. Python’s from_bytes() method is used to convert each of the returned values back to the original integer.
Finally, if you are storing a negative integer, the same approach is used but you must add another arg to indicate this integer is signed. The example only shows converting a negative integer to a byte string and back.
1 >>> integer = 12345
2 >>> integer_as_4bytes = integer.to_bytes(4, 'big')
3 >>> integer_as_32bytes = integer.to_bytes(32, 'big')
4 >>> integer_as_byte_array = integer.to_bytes(2, 'big')
5 >>> trx_receipt = t.run_trx(user, 'storeBytes', integer_as_4bytes, integer_as_32bytes, integer_as_byte_array)
6 >>> t.get_var('testBytes4')
7 b'\x00\x0009'
8 >>> values = t.call_fcn('getBytes')
9 >>> values
10 [b'\x00\x0009', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0009', b'09']
11 >>> int.from_bytes(values[0], byteorder='big')
12 12345
13 >>> int.from_bytes(values[1], byteorder='big')
14 12345
15 >>> int.from_bytes(values[2], byteorder='big')
16 12345
17 >>>
18 >>> neg_integer = -42
19 >>> bytes = neg_integer.to_bytes(4, 'big', signed=True)
20 >>> bytes
21 b'\xff\xff\xff\xd6'
22 >>> int.from_bytes(bytes, 'big', signed=True)
23 -42
Set a hex value
If you need to store a string of hex characters, you can use the fromhex() method to place the hex equivalent in a Python byte string and use that as an arg to a Solidity transaction. The example below creates a four bytes from eight hex characters and stores into the three different bytes variables. The example finishes by getting the four-byte public state variable value holding the eight hex characters as well as the returned values from all three variables.
1 >>> hex_string = 'AAAABBBB'
2 >>> hex_string_as_bytes = bytes.fromhex(hex_string)
3 >>> trx_receipt = t.run_trx(user, 'storeBytes', hex_string_as_bytes, hex_string_as_bytes, hex_string_as_bytes)
4 >>> t.get_var('testBytes4')
5 b'\xaa\xaa\xbb\xbb'
6 >>> t.call_fcn('getBytes')
7 [b'\xaa\xaa\xbb\xbb', b'\xaa\xaa\xbb\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\xaa\xaa\xbb\xbb']
Using a Python bytearray as an arg
When passing a value as an argument for a Solidity byte parameter you can use a Python bytearray. The example below passes byte arrays to both the fixed and variable byte args. A three-byte bytearray is passed to the two byteN parameters. A longer bytearray is passed to the bytes parameter.
1 >>> short_string = 'abc'
2 >>> short_string_bytes = string.encode()
3 >>> short_string_bytes
4 b'abc'
5 >>> short_string_bytearray = bytearray(short_string_bytes)
6 >>> short_string_bytearray
7 bytearray(b'abc')
8 >>> long_string = 'Life, the universe, and everything'
9 >>> long_string_bytes = long_string.encode()
10 >>> long_string_bytes
11 b'Life, the universe, and everything'
12 >>> long_string_bytearray = bytearray(long_string_bytes)
13 >>> long_string_bytearray
14 bytearray(b'Life, the universe, and everything')
15 >>> trx_receipt = t.run_trx(user, 'storeBytes', short_string_bytearray, short_string_bytearray, long_string_bytearray)
16 >>> t.call_fcn('getBytes')
17 [b'abc\x00', b'abc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'Life, the universe, and everything']'
The choice of using a Python byte string versus a bytearray is up to the needs of your Python code. The bytearray allows more manipulation; it is mutable. The byte object is immutable.
Exception if arg has too many bytes
If you attempt to pass an arg with more bytes than the Solidity size
of a fixed array byte variable, the transaction will revert.
The example below uses SimplethError
to catch the exception
thrown when an eight-byte value is passed to the four-byte arg;
this is the wrong arg type - it is not bytes4.
1 >>> from simpleth import SimplethError
2 >>> string = 'testtest'
3 >>> eight_bytes = string.encode()
4 >>> eight_bytes
5 b'testtest'
6 >>> try:
7 ... t.run_trx(user, 'storeBytes', eight_bytes, eight_bytes, eight_bytes)
8 ... except SimplethError as excp:
9 ... print(excp.message)
10 ...
11 ERROR in Test().submit_trx(storeBytes): Wrong number or type of args"".
12 HINT1: Check parameter definition(s) for the transaction in the contract.
13 HINT2: Check run_trx() optional parameter types.
14 HINT3: For bytesN parameter, check you do not pass more than N bytes.
It’s OK to pass an arg with fewer bytes than the fixed byte array size, an earlier example uses a 3-byte arg for a bytes4 value. The trailing bytes will be set to null. But, as shown here, you are not allowed to pass too any bytes.
More fun with hex values
1) string of hex -> byte string of hex values -> string of hex
1 >>> hex_string = 'AB1D'
2 >>> bytes.fromhex(hex_string)
3 b'\xab\x1d'
4 >>> bytes.fromhex(hex_string).hex()
5 'ab1d'
2) string of text -> byte string of text -> string of hex values -> string of text
1 >>> string = 'test'
2 >>> string_as_bytes = string.encode()
3 >>> string_as_bytes
4 b'test'
5 >>> string_as_bytes.hex()
6 '74657374'
7 >>> bytes.fromhex(string_as_hex).decode('ASCII')
8 'test'
3) returned bytes -> integer
1 >>> values[0]
2 b'\x00\x0009'
3 >>> values[0].hex() # take a look at hex in the bytes
4 '00003039'
5 >>> int('0x' + values[0].hex(), base=16) # convert to integer
6 12345
4) byte string -> byte string padded with trailing nulls
If you need to compare the returned value for a fixed byte array, here’s one approach to add the trailing nulls. Example makes it match a bytes32. This might come in handy, for example, when testing an event arg of a bytes value.
1 >>> string = 'abc'
2 >>> string_as_bytes = string.encode()
3 >>> string_as_bytes
4 b'abc'
5 >>> string_as_bytes32 = bytearray(string_as_bytes) + bytearray(32 - len(string_as_bytes))
6 >>> string_as_bytes32
7 bytearray(b'abc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
Handling time
simpleth
provides support for handing time, especially
epoch time:
simpleth.Convert.epoch_time()
returns the current time in epoch seconds.simpleth.Convert.local_time_string()
returns the current time as a string.simpleth.Convert.to_local_time_string()
converts epoch seconds to a time string.
1 >>> v.local_time_string()
2 '2022-05-21 18:03:41'
3 >>> v.local_time_string('%A %I:%M:%S %p')
4 'Saturday 06:04:19 PM'
5
6 >>> now = v.epoch_time()
7 >>> now
8 1653175079.5026972
9 >>> v.to_local_time_string(now)
10 '2022-05-21 18:17:59'
11 >>> v.to_local_time_string(now, '%A %I:%M:%S %p')
12 'Saturday 06:17:59 PM'
13
14 >>> receipt = c.run_trx(user, 'storeNums', 3, 5, 7)
15 >>> r = Results(c, receipt)
16 >>> r.block_time_epoch
17 1653175121
18 >>> r.event_args[0]['timestamp']
19 1653175121
20 >>> v.to_local_time_string(r.block_time_epoch)
21 '2022-05-21 18:18:41'
22 >>> v.to_local_time_string(r.event_args[0]['timestamp'])
23 '2022-05-21 18:18:41'
Note
Line 1: Get the current time using the default time string format.
Line 2: Get the current time and specify the time string format codes.
Line 6: Get the current time in epoch seconds. It is shown on line 8.
Line 9: Convert that epoch time to the default time string.
Line 10: Convert it to the specified format.
Line 14: Run the usual transaction to show how time conversion might help. So far, we’ve always seen timestamps in epoch seconds. Converting to a time format string may make them more useful.
Line 17: Shows the transaction’s block time in epoch seconds.
Line 21: Shows that block time in a time format string.
Line 19: Same for the
NumsStored
arg for the contract’sblock.timestamp
. Here’s the epoch seconds used by Solidity and line 23 converts it to a time string.
See the list of Python Time String Format Codes for details on directives available for the strings.
SimplethError exceptions
simpleth.SimplethError
throws exceptions for errors in all
simpleth
classes. The intent is to let you code to catch this
single exception to simplify error-handling and provide hints to
quickly identify the cause of the error.
1 >>> c = Contract('bogus')
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 File "C:\Users\snewe\OneDrive\Desktop\simpleth\src\simpleth\simpleth.py", line 943, in __init__
5 self._abi: List = self._get_artifact_abi()
6 File "C:\Users\snewe\OneDrive\Desktop\simpleth\src\simpleth\simpleth.py", line 2151, in _get_artifact_abi
7 raise SimplethError(message, code='C-100-010') from None
8 simpleth.SimplethError: [C-100-010] ERROR in bogus()._get_artifact_abi(). Unable to read ABI file.
9 Full path: C:/Users/snewe/OneDrive/Desktop/simpleth/artifacts/bogus.abi
10 Contract name of "bogus" is bad.
11 HINT 1: Check the spelling of the contract name.
12 HINT 2: You may need to do a new compile.
Note
Line 1: Cause an exception with a bad contract name. This is the typical type of message you will see when using the Python interpreter.
Line 8: This is the start of the
SimplethError
message and hints on possible causes.
1 >>> try:
2 ... c = Contract('bogus')
3 ... except SimplethError as e:
4 ... print(e)
5 ...
6 [C-100-010] ERROR in bogus()._get_artifact_abi(). Unable to read ABI file.
7 Full path: C:/Users/snewe/OneDrive/Desktop/simpleth/artifacts/bogus.abi
8 Contract name of "bogus" is bad.
9 HINT 1: Check the spelling of the contract name.
10 HINT 2: You may need to do a new compile.
Note
Line 1: Use a
try
/except
around the line to create theContract
object.Line 4: Our only action with the exception is to print it. A program could take action to fix the problem at this point.
1 >>> import pprint
2 >>> pp = pprint.PrettyPrinter(indent = 2)
3 >>> try:
4 ... c = Contract('bogus')
5 ... except SimplethError as e:
6 ... print(f'code = \n{e.code}')
7 ... print(f'message = \n{e.message}')
8 ... print(f'revert_msg = \n{e.revert_msg}')
9 ... print(f'exc_info =')
10 ... pp.pprint({e.exc_info})
11 ...
12 code =
13 C-100-010
14 message =
15 ERROR in bogus()._get_artifact_abi(). Unable to read ABI file.
16 Full path: C:/Users/snewe/OneDrive/Desktop/simpleth/artifacts/bogus.abi
17 Contract name of "bogus" is bad.
18 HINT 1: Check the spelling of the contract name.
19 HINT 2: You may need to do a new compile.
20
21 revert_msg =
22
23 exc_info =
24 { ( <class 'FileNotFoundError'>,
25 FileNotFoundError(2, 'No such file or directory'),
26 <traceback object at 0x00000231A2CDE6C0>)}
Note
Line 6: There are three properties you can access. First is the unique
code
string for the exception. It is accessed here and its value is printed on line 13.Line 5: The text of the error message is accessed here and printed on lines 15 through 20.
Line 8: The
revert_msg
is sent back from a transaction that had arequire()
that failed or arevert()
. Otherwise, it is empty. Our empty string is shown on line 22.Line 10: The exception information is accessed here and pretty printed on lines 24 through 26.
You can access these properties instead of the entire message if
that suits your purpose better in handling simpleth
errors.
Transaction exceptions
Exceptions can be thrown by the Solidity Ethereum Virtual Machine (EVM) that runs the transaction when it encounters runtime errors such as:
divide by zero
out of bounds array index
out of gas
out of range enum value
ether sent to a non-payable transaction
transaction sender was not valid
insufficient ether in sender balance to run the transaction
These transaction error exceptions will throw SimplethError
exceptions for your code to handle.
Other exceptions can be thrown by the EVM which are coded into a transaction. A contract may be checking for conditions where the transaction should not be allowed to proceed and needs to be reverted. The transaction can:
Use the Solidity operation,
require()
, to validate a condition is met. If the condition is not met, arevert()
is done and an optional message string will be available in theSimplethError
require()
is commonly used in a contractmodifier
and a frequent type of modifier is to limit access to a transaction to one or more specified accounts.Use of the Solidity operation,
assert()
, to confirm an expected condition. There is no message for a failed assert.assert()
is commonly used to double-check a value meets your expectations and should never fail.Use of the Solidity operation,
revert()
, will cause the transaction to stop and exit. There is no message for a revert.revert()
is used if conditions warrant stopping and undoing all actions by the transaction.
These transaction exceptions will cause SimplethError
exceptions for your code to handle.
We’ll go through some examples. First up is what a transaction error
exception thrown by an out of bounds index value looks like in the
Python interpreter and how it might look in your code with a
try
/ except
:
1 >>> c.run_trx(user, 'storeNum', 4, 42)
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 File "C:\Users\snewe\OneDrive\Desktop\simpleth\src\simpleth\simpleth.py", line 1838, in run_trx
5 trx_hash: T_HASH = self.submit_trx(
6 File "C:\Users\snewe\OneDrive\Desktop\simpleth\src\simpleth\simpleth.py", line 2128, in submit_trx
7 f'HINT11: Was max_priority_fee_gwei a float? (It must be an int)\n'
8 simpleth.SimplethError: [C-080-080] ERROR in Test().submit_trx(storeNum).
9 ValueError says: VM Exception while processing transaction: revert
10 HINT1: Did you fail to pass a transaction require()?
11 HINT2: Did you fail to pass a transaction guard modifier()?
12 HINT3: Did you fail an assert()?
13 HINT4: Did the transaction do a revert()?
14 HINT5: Did you divide by zero?
15 HINT6: Did you pass in an out-of-bounds array index?
16 HINT7: Did you pass in an out-of-range enum value?
17 HINT8: Was the gas limit too low (less than the base fee)?
18 HINT9: Was the gas limit too high (greater than the block gas limit)?
19 HINT10: Was max_fee_gwei a float? (It must be an int)
20 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
21 HINT12: Did this trx call another trx, which failed?
22 HINT13: Did you attempt to send ether to a non-payable trx?
23 HINT14: Was sender a valid account that can submit a trx?
24 HINT15: Does sender have enough Ether to run trx?
25
26 >>> try:
27 ... c.run_trx(user, 'storeNum', 4, 42)
28 ... except SimplethError as e:
29 ... print(e.code)
30 ... print(e.message)
31 ... print(e.revert_description)
32 ... pp.pprint(e.exc_info)
33 ...
34 C-080-080
35 ERROR in Test().submit_trx(storeNum).
36 ValueError says: VM Exception while processing transaction: revert
37 HINT1: Did you fail to pass a transaction require()?
38 HINT2: Did you fail to pass a transaction guard modifier()?
39 HINT3: Did you fail an assert()?
40 HINT4: Did the transaction do a revert()?
41 HINT5: Did you divide by zero?
42 HINT6: Did you pass in an out-of-bounds array index?
43 HINT7: Did you pass in an out-of-range enum value?
44 HINT8: Was the gas limit too low (less than the base fee)?
45 HINT9: Was the gas limit too high (greater than the block gas limit)?
46 HINT10: Was max_fee_gwei a float? (It must be an int)
47 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
48 HINT12: Did this trx call another trx, which failed?
49 HINT13: Did you attempt to send ether to a non-payable trx?
50 HINT14: Was sender a valid account that can submit a trx?
51 HINT15: Does sender have enough Ether to run trx?
52
53
54 ( <class 'ValueError'>,
55 ValueError({'message': 'VM Exception while processing transaction: revert', 'code': -32000, 'data': {'0x6f829f521ebd6bf7ab34feea51bb4c18b82c663229004af13fa4ea788f0117d9': {'error': 'revert', 'program_counter': 5528, 'return': '0x4e487b710000000000000000000000000000000000000000000000000000000000000032'}, 'stack': 'RuntimeError: VM Exception while processing transaction: revert\n at Function.RuntimeError.fromResults (C:
56 f'HINT11: Was max_priority_fee_gwei a float? (It must be an int)\n'
57 simpleth.SimplethError: [C-080-080] ERROR in Test().submit_trx(storeNum).
58 ValueError says: VM Exception while processing transaction: revert
59 HINT1: Did you fail to pass a transaction require()?
60 HINT2: Did you fail to pass a transaction guard modifier()?
61 HINT3: Did you fail an assert()?
62 HINT4: Did the transaction do a revert()?
63 HINT5: Did you divide by zero?
64 HINT6: Did you pass in an out-of-bounds array index?
65 HINT7: Did you pass in an out-of-range enum value?
66 HINT8: Was the gas limit too low (less than the base fee)?
67 HINT9: Was the gas limit too high (greater than the block gas limit)?
68 HINT10: Was max_fee_gwei a float? (It must be an int)
69 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
70 HINT12: Did this trx call another trx, which failed?
71 HINT13: Did you attempt to send ether to a non-payable trx?
72 HINT14: Was sender a valid account that can submit a trx?
73 HINT15: Does sender have enough Ether to run trx?
74
75 >>> try:
76 ... c.run_trx(user, 'storeNum', 4, 42)
77 ... except SimplethError as e:
78 ... print(e.code)
79 ... print(e.message)
80 ... print(e.revert_description)
81 ... pp.pprint(e.exc_info)
82 ...
83 C-080-080
84 ERROR in Test().submit_trx(storeNum).
85 ValueError says: VM Exception while processing transaction: revert
86 HINT1: Did you fail to pass a transaction require()?
87 HINT2: Did you fail to pass a transaction guard modifier()?
88 HINT3: Did you fail an assert()?
89 HINT4: Did the transaction do a revert()?
90 HINT5: Did you divide by zero?
91 HINT6: Did you pass in an out-of-bounds array index?
92 HINT7: Did you pass in an out-of-range enum value?
93 HINT8: Was the gas limit too low (less than the base fee)?
94 HINT9: Was the gas limit too high (greater than the block gas limit)?
95 HINT10: Was max_fee_gwei a float? (It must be an int)
96 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
97 HINT12: Did this trx call another trx, which failed?
98 HINT13: Did you attempt to send ether to a non-payable trx?
99 HINT14: Was sender a valid account that can submit a trx?
100 HINT15: Does sender have enough Ether to run trx?
101
102
103 ( <class 'ValueError'>,
104 ValueError({'message': 'VM Exception while processing transaction: revert', 'code': -32000, 'data': {'0x6f829f521ebd6bf7ab34feea51bb4c18b82c663229004af13fa4ea788f0117d9': {'error': 'revert', 'program_counter': 5528, 'return': '0x4e487b710000000000000000000000000000000000000000000000000000000000000032'}, 'stack': 'RuntimeError: VM Exception while processing transaction: revert\n at Function.RuntimeError.fromResults (C:
105 f'HINT11: Was max_priority_fee_gwei a float? (It must be an int)\n'
106 simpleth.SimplethError: [C-080-080] ERROR in Test().submit_trx(storeNum).
107 ValueError says: VM Exception while processing transaction: revert
108 HINT1: Did you fail to pass a transaction require()?
109 HINT2: Did you fail to pass a transaction guard modifier()?
110 HINT3: Did you fail an assert()?
111 HINT4: Did the transaction do a revert()?
112 HINT5: Did you divide by zero?
113 HINT6: Did you pass in an out-of-bounds array index?
114 HINT7: Did you pass in an out-of-range enum value?
115 HINT8: Was the gas limit too low (less than the base fee)?
116 HINT9: Was the gas limit too high (greater than the block gas limit)?
117 HINT10: Was max_fee_gwei a float? (It must be an int)
118 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
119 HINT12: Did this trx call another trx, which failed?
120 HINT13: Did you attempt to send ether to a non-payable trx?
121 HINT14: Was sender a valid account that can submit a trx?
122 HINT15: Does sender have enough Ether to run trx?
123
124 >>> try:
125 ... c.run_trx(user, 'storeNum', 4, 42)
126 ... except SimplethError as e:
127 ... print(e.code)
128 ... print(e.message)
129 ... print(e.revert_msg)
130 ... pp.pprint(e.exc_info)
131 ...
132 C-080-080
133 ERROR in Test().submit_trx(storeNum).
134 ValueError says: VM Exception while processing transaction: revert
135 HINT1: Did you fail to pass a transaction require()?
136 HINT2: Did you fail to pass a transaction guard modifier()?
137 HINT3: Did you fail an assert()?
138 HINT4: Did the transaction do a revert()?
139 HINT5: Did you divide by zero?
140 HINT6: Did you pass in an out-of-bounds array index?
141 HINT7: Did you pass in an out-of-range enum value?
142 HINT8: Was the gas limit too low (less than the base fee)?
143 HINT9: Was the gas limit too high (greater than the block gas limit)?
144 HINT10: Was max_fee_gwei a float? (It must be an int)
145 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
146 HINT12: Did this trx call another trx, which failed?
147 HINT13: Did you attempt to send ether to a non-payable trx?
148 HINT14: Was sender a valid account that can submit a trx?
149 HINT15: Does sender have enough Ether to run trx?
150
151
152 ( <class 'ValueError'>,
153 ValueError({'message': 'VM Exception while processing transaction: revert', 'code': -32000, 'data': {'0x6f829f521ebd6bf7ab34feea51bb4c18b82c663229004af13fa4ea788f0117d9': {'error': 'revert', 'program_counter': 5528, 'return': '0x4e487b710000000000000000000000000000000000000000000000000000000000000032'}, 'stack': 'RuntimeError: VM Exception while processing transaction: revert\n at Function.RuntimeError.fromResults (C:\\Program Files\\WindowsApps\\GanacheUI_2.5.4.0_x64__5dg5pnz03psnj\\app\\resources\\static\\node\\node_modules\\ganache-core\\lib\\utils\\runtimeerror.js:94:13)\n at BlockchainDouble.processBlock (C:\\Program Files\\WindowsApps\\GanacheUI_2.5.4.0_x64__5dg5pnz03psnj\\app\\resources\\static\\node\\node_modules\\ganache-core\\lib\\blockchain_double.js:627:24)\n at processTicksAndRejections (internal/process/task_queues.js:93:5)', 'name': 'RuntimeError'}}),
154 <traceback object at 0x00000231A2E161C0>)
Note
Line 1: Let’s cause the VM to throw an exception due to an out of bounds array index. Here we are asking
storeNum
to put the value of 42 intonums[4]
. This is a bad index value.nums[]
only has 3 elements.Line 2: You see the Python interpreter output with the exception. The error output ends on line 25.
Line 26: In a Python program, you might put the statement in a
try
/except
. The example prints out the properties you could access. Your code would probably take steps to notify the user of the error or other code to handle the problem; not just print error info.Line 34: This is the error code for transaction error exceptions. (The Hints list covers the usual causes.)
Line 35: This is the start of the error message text created by
simpleth
. The message text ends on line 52.Line 53: This is the transaction’s revert message. It is an empty string for an oob (out-of-bounds) error.
Line 53: This is the pretty print of the exception info property. A
ValueError
caused an exception. SimplethError caught it and threw its exception with a lot of added info. This lets you see the original info from the first exception.
Next up, let’s start looking at exceptions that are coded into the Test
contract. The transaction, sumTwoNums
, has a require()
that checks
for the owner
of the contract to be the address that sent the transaction,
i.e., the owner is the only one allowed to use this transaction. The
require()
has a message that explains the problem.
1 >>> c.run_trx(user, 'sumTwoNums')
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 File "C:\Users\snewe\OneDrive\Desktop\simpleth\src\simpleth\simpleth.py", line 1838, in run_trx
5 trx_hash: T_HASH = self.submit_trx(
6 File "C:\Users\snewe\OneDrive\Desktop\simpleth\src\simpleth\simpleth.py", line 2128, in submit_trx
7 f'HINT11: Was max_priority_fee_gwei a float? (It must be an int)\n'
8 simpleth.SimplethError: [C-080-080] ERROR in Test().submit_trx(sumTwoNums).
9 ValueError says: VM Exception while processing transaction: revert must be owner to sum two nums
10 HINT1: Did you fail to pass a transaction require()?
11 HINT2: Did you fail to pass a transaction guard modifier()?
12 HINT3: Did you fail an assert()?
13 HINT4: Did the transaction do a revert()?
14 HINT5: Did you divide by zero?
15 HINT6: Did you pass in an out-of-bounds array index?
16 HINT7: Did you pass in an out-of-range enum value?
17 HINT8: Was the gas limit too low (less than the base fee)?
18 HINT9: Was the gas limit too high (greater than the block gas limit)?
19 HINT10: Was max_fee_gwei a float? (It must be an int)
20 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
21 HINT12: Did this trx call another trx, which failed?
22 HINT13: Did you attempt to send ether to a non-payable trx?
23 HINT14: Was sender a valid account that can submit a trx?
24 HINT15: Does sender have enough Ether to run trx?
25
26 >>> try:
27 ... c.run_trx(user, 'sumTwoNums')
28 ... except SimplethError as e:
29 ... msg = e.revert_description
30 ...
31 >>> msg
32 'must be owner to sum two nums'
33 f'HINT11: Was max_priority_fee_gwei a float? (It must be an int)\n'
34 simpleth.SimplethError: [C-080-080] ERROR in Test().submit_trx(sumTwoNums).
35 ValueError says: VM Exception while processing transaction: revert must be owner to sum two nums
36 HINT1: Did you fail to pass a transaction require()?
37 HINT2: Did you fail to pass a transaction guard modifier()?
38 HINT3: Did you fail an assert()?
39 HINT4: Did the transaction do a revert()?
40 HINT5: Did you divide by zero?
41 HINT6: Did you pass in an out-of-bounds array index?
42 HINT7: Did you pass in an out-of-range enum value?
43 HINT8: Was the gas limit too low (less than the base fee)?
44 HINT9: Was the gas limit too high (greater than the block gas limit)?
45 HINT10: Was max_fee_gwei a float? (It must be an int)
46 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
47 HINT12: Did this trx call another trx, which failed?
48 HINT13: Did you attempt to send ether to a non-payable trx?
49 HINT14: Was sender a valid account that can submit a trx?
50 HINT15: Does sender have enough Ether to run trx?
51
52 >>> try:
53 ... c.run_trx(user, 'sumTwoNums')
54 ... except SimplethError as e:
55 ... msg = e.revert_description
56 ...
57 >>> msg
58 'must be owner to sum two nums'
59 f'HINT11: Was max_priority_fee_gwei a float? (It must be an int)\n'
60 simpleth.SimplethError: [C-080-080] ERROR in Test().submit_trx(sumTwoNums).
61 ValueError says: VM Exception while processing transaction: revert must be owner to sum two nums
62 HINT1: Did you fail to pass a transaction require()?
63 HINT2: Did you fail to pass a transaction guard modifier()?
64 HINT3: Did you fail an assert()?
65 HINT4: Did the transaction do a revert()?
66 HINT5: Did you divide by zero?
67 HINT6: Did you pass in an out-of-bounds array index?
68 HINT7: Did you pass in an out-of-range enum value?
69 HINT8: Was the gas limit too low (less than the base fee)?
70 HINT9: Was the gas limit too high (greater than the block gas limit)?
71 HINT10: Was max_fee_gwei a float? (It must be an int)
72 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
73 HINT12: Did this trx call another trx, which failed?
74 HINT13: Did you attempt to send ether to a non-payable trx?
75 HINT14: Was sender a valid account that can submit a trx?
76 HINT15: Does sender have enough Ether to run trx?
77
78 >>> try:
79 ... c.run_trx(user, 'sumTwoNums')
80 ... except SimplethError as e:
81 ... msg = e.revert_msg
82 ...
83 >>> msg
84 'must be owner to sum two nums'
Note
Line 1:
user
is not allowed to use this transaction. The transaction’srequire()
reverts, throws an exception, and sends back a message.Line 9: Shows the message. It has been passed back as part of the
ValueError
exception, whichSimplethError
catches.Line 26: Uses a
try
/except
to get the message from the failedrequire()
.Line 31:
msg
has the message explaining why the transaction was reverted.
Let’s look at a modifier that fails. Test
has a transaction, setOwner
that is guarded by a modifier isOwner
. This is implemented with a
require()
. This example is included because modifiers are very common
and you’ll see they act just like the previous example of a failed
require()
1 >>> try:
2 ... c.run_trx(user, 'setOwner', user)
3 ... except SimplethError as e:
4 ... msg = e.revert_description
5 ...
6 >>> msg
7 'Must be owner'
8
9 >>> try:
10 ... c.run_trx(user, 'setOwner', user)
11 ... except SimplethError as e:
12 ... msg = e.revert_description
13 ...
14 >>> msg
15 'Must be owner'
16
17 >>> try:
18 ... c.run_trx(user, 'setOwner', user)
19 ... except SimplethError as e:
20 ... msg = e.revert_msg
21 ...
22 >>> msg
23 'Must be owner'
Note
Line 2: This will fail the
isOwner
modifier since ouruser
account does not ownTest
.Line 4: Shows how to obtain the message coded in the contract for the
require()
used in themodifier
.Line 6: Your program could now show this error message to your user.
This example shows a failed assert()
. There is no message associated
with an assert. If the test fails, the transaction is reverted and a Python
ValueError
is thrown.
1 >>> c.run_trx(user, 'assertGreaterThan10', 9)
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 File "C:\Users\snewe\OneDrive\Desktop\simpleth\src\simpleth\simpleth.py", line 1838, in run_trx
5 trx_hash: T_HASH = self.submit_trx(
6 File "C:\Users\snewe\OneDrive\Desktop\simpleth\src\simpleth\simpleth.py", line 2121, in submit_trx
7 raise SimplethError(message, code='C-080-080') from None
8 simpleth.SimplethError: [C-080-080] ERROR in Test().submit_trx(assertGreaterThan10).
9 ValueError says:
10 HINT1: Did you fail to pass a transaction require()?
11 HINT2: Did you fail to pass a transaction guard modifier()?
12 HINT3: Did you fail an assert()?
13 HINT4: Did the transaction do a revert()?
14 HINT5: Did you divide by zero?
15 HINT6: Did you pass in an out-of-bounds array index?
16 HINT7: Did you pass in an out-of-range enum value?
17 HINT8: Was the gas limit too low (less than the base fee)?
18 HINT9: Was the gas limit too high (greater than the block gas limit)?
19 HINT10: Was max_fee_gwei a float? (It must be an int)
20 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
21 HINT12: Did this trx call another trx, which failed?
22 HINT13: Did you attempt to send ether to a non-payable trx?
23 HINT14: Was sender a valid account that can submit a trx?
24 HINT15: Does sender have enough Ether to run trx?
25
26 >>> try:
27 ... c.run_trx(user, 'assertGreaterThan10', 9)
28 ... except SimplethError as e:
29 ... pp.pprint(e.exc_info)
30 ...
31 ( <class 'ValueError'>,
32 ValueError({'message': 'VM Exception while processing transaction: revert', 'code': -32000, 'data': {'0x5d3bee4eee5c9b320eff083666910bf9ff0ab0bb9c9790f27226d4ec78685cb9': {'error': 'revert', 'program_counter': 5664, 'return': '0x4e487b710000000000000000000000000000000000000000000000000000000000000001'}, 'stack': 'RuntimeError: VM Exception while processing transaction: revert\n at Function.RuntimeError.fromResults (C:\\Program Files\\WindowsApps\\GanacheUI_2.5.4.0_x64__5dg5pnz03psnj\\app\\resources\\static\\node\\node_modules\\ganache-core\\lib\\utils\\runtimeerror.js:94:13)\n at BlockchainDouble.processBlock (C:\\Program Files\\WindowsApps\\GanacheUI_2.5.4.0_x64__5dg5pnz03psnj\\app\\resources\\static\\node\\node_modules\\ganache-core\\lib\\blockchain_double.js:627:24)\n at runMicrotasks (<anonymous>)\n at processTicksAndRejections (internal/process/task_queues.js:93:5)', 'name': 'RuntimeError'}}),
33 <traceback object at 0x000001DE3411BAC0>)
Note
Line 1: This transaction will fail its
assert()
. We are passing in an arg of 9. The assert requires that arg to be greater than 10.Line 2: This is the output in the interpreter and continues to line25. Note that there is nothing passed back in line 9. Unlike in some earlier examples where a message from the contract was shown.
Line 26: As before, use a
try
/except
to pretty print theValueError
exception info. There’s nothing unique to pass back to our user.
Finally, let’s look at what happens when a transaction uses a revert()
statement. Test
has a transaction, revertTransaction
with only
one statement, a revert()
. A revert()
can have a message. We’ll
look for it in the same manner we did for require()
:
1 >>> c.run_trx(user, 'revertTransaction')
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 File "C:\Users\snewe\OneDrive\Desktop\simpleth\src\simpleth\simpleth.py", line 1838, in run_trx
5 trx_hash: T_HASH = self.submit_trx(
6 File "C:\Users\snewe\OneDrive\Desktop\simpleth\src\simpleth\simpleth.py", line 2128, in submit_trx
7 f'HINT11: Was max_priority_fee_gwei a float? (It must be an int)\n'
8 simpleth.SimplethError: [C-080-080] ERROR in Test().submit_trx(revertTransaction).
9 ValueError says: VM Exception while processing transaction: revert Revert this transaction.
10 HINT1: Did you fail to pass a transaction require()?
11 HINT2: Did you fail to pass a transaction guard modifier()?
12 HINT3: Did you fail an assert()?
13 HINT4: Did the transaction do a revert()?
14 HINT5: Did you divide by zero?
15 HINT6: Did you pass in an out-of-bounds array index?
16 HINT7: Did you pass in an out-of-range enum value?
17 HINT8: Was the gas limit too low (less than the base fee)?
18 HINT9: Was the gas limit too high (greater than the block gas limit)?
19 HINT10: Was max_fee_gwei a float? (It must be an int)
20 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
21 HINT12: Did this trx call another trx, which failed?
22 HINT13: Did you attempt to send ether to a non-payable trx?
23 HINT14: Was sender a valid account that can submit a trx?
24 HINT15: Does sender have enough Ether to run trx?
25
26 >>> try:
27 ... c.run_trx(user, 'revertTransaction')
28 ... except SimplethError as e:
29 ... msg = e.revert_description
30 ...
31 >>> msg
32 'Revert this transaction.'
33 f'HINT11: Was max_priority_fee_gwei a float? (It must be an int)\n'
34 simpleth.SimplethError: [C-080-080] ERROR in Test().submit_trx(revertTransaction).
35 ValueError says: VM Exception while processing transaction: revert Revert this transaction.
36 HINT1: Did you fail to pass a transaction require()?
37 HINT2: Did you fail to pass a transaction guard modifier()?
38 HINT3: Did you fail an assert()?
39 HINT4: Did the transaction do a revert()?
40 HINT5: Did you divide by zero?
41 HINT6: Did you pass in an out-of-bounds array index?
42 HINT7: Did you pass in an out-of-range enum value?
43 HINT8: Was the gas limit too low (less than the base fee)?
44 HINT9: Was the gas limit too high (greater than the block gas limit)?
45 HINT10: Was max_fee_gwei a float? (It must be an int)
46 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
47 HINT12: Did this trx call another trx, which failed?
48 HINT13: Did you attempt to send ether to a non-payable trx?
49 HINT14: Was sender a valid account that can submit a trx?
50 HINT15: Does sender have enough Ether to run trx?
51
52 >>> try:
53 ... c.run_trx(user, 'revertTransaction')
54 ... except SimplethError as e:
55 ... msg = e.revert_description
56 ...
57 >>> msg
58 'Revert this transaction.'
59 f'HINT11: Was max_priority_fee_gwei a float? (It must be an int)\n'
60 simpleth.SimplethError: [C-080-080] ERROR in Test().submit_trx(revertTransaction).
61 ValueError says: VM Exception while processing transaction: revert Revert this transaction.
62 HINT1: Did you fail to pass a transaction require()?
63 HINT2: Did you fail to pass a transaction guard modifier()?
64 HINT3: Did you fail an assert()?
65 HINT4: Did the transaction do a revert()?
66 HINT5: Did you divide by zero?
67 HINT6: Did you pass in an out-of-bounds array index?
68 HINT7: Did you pass in an out-of-range enum value?
69 HINT8: Was the gas limit too low (less than the base fee)?
70 HINT9: Was the gas limit too high (greater than the block gas limit)?
71 HINT10: Was max_fee_gwei a float? (It must be an int)
72 HINT11: Was max_priority_fee_gwei a float? (It must be an int)
73 HINT12: Did this trx call another trx, which failed?
74 HINT13: Did you attempt to send ether to a non-payable trx?
75 HINT14: Was sender a valid account that can submit a trx?
76 HINT15: Does sender have enough Ether to run trx?
77
78 >>> try:
79 ... c.run_trx(user, 'revertTransaction')
80 ... except SimplethError as e:
81 ... msg = e.revert_msg
82 ...
83 >>> msg
84 'Revert this transaction.'
Note
Line 1: Call the transaction that always reverts.
Line 9: Here’s the way the revert message will appear in the interpreter.
Line 32: Here’s the revert message.
Selfdestruct
Solidity includes the selfdestruct()
function.
The Test
contract includes a transaction, destroy()
which issues selfdestruct
and makes the contract unusable. As far as
simpleth
goes this is just another transaction, but it makes for
an interesting example:
1 >>> b.balance_of(c.address)
2 610
3 >>> b.balance(b.accounts[3])
4 99889613060000000010
5 >>> receipt = c.run_trx(owner, 'destroy', b.accounts[3])
6
7 >>> b.balance(c.address)
8 0
9 >>> b.balance(b.accounts[3])
10 99889613060000000620
11 >>> c.get_var('owner')
12 Traceback (most recent call last):
13 ... snip ...
14 simpleth.SimplethError: [C-060-020] ERROR in Test().getvar(): Unable to get variable owner.
15 BadFunctionCallOutput says Could not transact with/call contract function, is contract deployed correctly and chain synced?
16 HINT1: Has contract been destroyed with selfdestruct()?
17 HINT2: Has contract not yet been deployed on a new chain?
18
19 >>> c.call_fcn('getNums')
20 Traceback (most recent call last):
21 File "<stdin>", line 1, in <module>
22 File "C:
23
24 >>> b.balance_of(c.address)
25 610
26 >>> b.balance(b.accounts[3])
27 99889613060000000010
28 >>> receipt = c.run_trx(owner, 'destroy', b.accounts[3])
29
30 >>> b.balance(c.address)
31 0
32 >>> b.balance(b.accounts[3])
33 99889613060000000620
34 >>> c.get_var('owner')
35 Traceback (most recent call last):
36 ... snip ...
37 simpleth.SimplethError: [C-060-020] ERROR in Test().getvar(): Unable to get variable owner.
38 BadFunctionCallOutput says Could not transact with/call contract function, is contract deployed correctly and chain synced?
39 HINT1: Has contract been destroyed with selfdestruct()?
40 HINT2: Has contract not yet been deployed on a new chain?
41
42 >>> c.call_fcn('getNums')
43 Traceback (most recent call last):
44 File "<stdin>", line 1, in <module>
45 File "C:
46
47 >>> b.balance(c.address)
48 610
49 >>> b.balance_of(b.accounts[3])
50 99889613060000000010
51 >>> receipt = c.run_trx(owner, 'destroy', b.accounts[3])
52
53 >>> b.balance(c.address)
54 0
55 >>> b.balance(b.accounts[3])
56 99889613060000000620
57 >>> c.get_var('owner')
58 Traceback (most recent call last):
59 ... snip ...
60 simpleth.SimplethError: [C-060-020] ERROR in Test().getvar(): Unable to get variable owner.
61 BadFunctionCallOutput says Could not transact with/call contract function, is contract deployed correctly and chain synced?
62 HINT1: Has contract been destroyed with selfdestruct()?
63 HINT2: Has contract not yet been deployed on a new chain?
64
65 >>> c.call_fcn('getNums')
66 Traceback (most recent call last):
67 File "<stdin>", line 1, in <module>
68 File "C:
69
70 >>> b.balance(c.address)
71 610
72 >>> b.balance_of(b.accounts[3])
73 99889613060000000010
74 >>> receipt = c.run_trx(owner, 'destroy', b.accounts[3])
75
76 >>> b.balance(c.address)
77 0
78 >>> b.balance(b.accounts[3])
79 99889613060000000620
80 >>> c.get_var('owner')
81 Traceback (most recent call last):
82 ... snip ...
83 simpleth.SimplethError: [C-060-020] ERROR in Test().getvar(): Unable to get variable owner.
84 BadFunctionCallOutput says Could not transact with/call contract function, is contract deployed correctly and chain synced?
85 HINT1: Has contract been destroyed with selfdestruct()?
86 HINT2: Has contract not yet been deployed on a new chain?
87
88 >>> c.call_fcn('getNums')
89 Traceback (most recent call last):
90 File "<stdin>", line 1, in <module>
91 File "C:
92
93 >>> b.balance(c.address)
94 610
95 >>> b.balance(b.accounts[3])
96 99889613060000000010
97 >>> receipt = c.run_trx(owner, 'destroy', b.accounts[3])
98
99 >>> b.balance(c.address)
100 0
101 >>> b.balance_of(b.accounts[3])
102 99889613060000000620
103 >>> c.get_var('owner')
104 Traceback (most recent call last):
105 ... snip ...
106 simpleth.SimplethError: [C-060-020] ERROR in Test().getvar(): Unable to get variable owner.
107 BadFunctionCallOutput says Could not transact with/call contract function, is contract deployed correctly and chain synced?
108 HINT1: Has contract been destroyed with selfdestruct()?
109 HINT2: Has contract not yet been deployed on a new chain?
110
111 >>> c.call_fcn('getNums')
112 Traceback (most recent call last):
113 File "<stdin>", line 1, in <module>
114 File "C:
115
116 >>> b.balance(c.address)
117 610
118 >>> b.balance(b.accounts[3])
119 99889613060000000010
120 >>> receipt = c.run_trx(owner, 'destroy', b.accounts[3])
121
122 >>> b.balance(c.address)
123 0
124 >>> b.balance_of(b.accounts[3])
125 99889613060000000620
126 >>> c.get_var('owner')
127 Traceback (most recent call last):
128 ... snip ...
129 simpleth.SimplethError: [C-060-020] ERROR in Test().getvar(): Unable to get variable owner.
130 BadFunctionCallOutput says Could not transact with/call contract function, is contract deployed correctly and chain synced?
131 HINT1: Has contract been destroyed with selfdestruct()?
132 HINT2: Has contract not yet been deployed on a new chain?
133
134 >>> c.call_fcn('getNums')
135 Traceback (most recent call last):
136 File "<stdin>", line 1, in <module>
137 File "C:
138
139 >>> b.balance(c.address)
140 610
141 >>> b.balance(b.accounts[3])
142 99889613060000000010
143 >>> receipt = c.run_trx(owner, 'destroy', b.accounts[3])
144
145 >>> b.balance_of(c.address)
146 0
147 >>> b.balance(b.accounts[3])
148 99889613060000000620
149 >>> c.get_var('owner')
150 Traceback (most recent call last):
151 ... snip ...
152 simpleth.SimplethError: [C-060-020] ERROR in Test().getvar(): Unable to get variable owner.
153 BadFunctionCallOutput says Could not transact with/call contract function, is contract deployed correctly and chain synced?
154 HINT1: Has contract been destroyed with selfdestruct()?
155 HINT2: Has contract not yet been deployed on a new chain?
156
157 >>> c.call_fcn('getNums')
158 Traceback (most recent call last):
159 File "<stdin>", line 1, in <module>
160 File "C:
161
162 >>> b.balance(c.address)
163 610
164 >>> b.balance(b.accounts[3])
165 99889613060000000010
166 >>> receipt = c.run_trx(owner, 'destroy', b.accounts[3])
167
168 >>> b.balance_of(c.address)
169 0
170 >>> b.balance(b.accounts[3])
171 99889613060000000620
172 >>> c.get_var('owner')
173 Traceback (most recent call last):
174 ... snip ...
175 simpleth.SimplethError: [C-060-020] ERROR in Test().getvar(): Unable to get variable owner.
176 BadFunctionCallOutput says Could not transact with/call contract function, is contract deployed correctly and chain synced?
177 HINT1: Has contract been destroyed with selfdestruct()?
178 HINT2: Has contract not yet been deployed on a new chain?
179
180 >>> c.call_fcn('getNums')
181 Traceback (most recent call last):
182 File "<stdin>", line 1, in <module>
183 File "C:
184
185 >>> b.balance(c.address)
186 610
187 >>> b.balance(b.accounts[3])
188 99889613060000000010
189 >>> receipt = c.run_trx(owner, 'destroy', b.accounts[3])
190
191 >>> b.balance(c.address)
192 0
193 >>> b.balance(b.accounts[3])
194 99889613060000000620
195 >>> c.get_var('owner')
196 Traceback (most recent call last):
197 ... snip ...
198 simpleth.SimplethError: [C-060-020] ERROR in Test().getvar(): Unable to get variable owner.
199 BadFunctionCallOutput says Could not transact with/call contract function, is contract deployed correctly and chain synced?
200 HINT1: Has contract been destroyed with selfdestruct()?
201 HINT2: Has contract not yet been deployed on a new chain?
202
203 >>> c.call_fcn('getNums')
204 Traceback (most recent call last):
205 File "<stdin>", line 1, in <module>
206 File "C:\Users\snewe\OneDrive\Desktop\simpleth\src\simpleth\simpleth.py", line 1253, in call_fcn
207 raise SimplethError(message, code='C-010-030') from None
208 simpleth.SimplethError: [C-010-030] ERROR in Test().call_fcn().
209 Unable to call function getNums.
210 BadFunctionCallOutput says Could not transact with/call contract function, is contract deployed correctly and chain synced?
211 HINT1: Has contract been destroyed with a selfdestruct()?
212 HINT2: Does contract need a new deploy?
213
214 >>> receipt = c.run_trx(user, 'storeNums', 2, 4, 6)
215 >>> print(Results(c, receipt))
216 Block number = 7580
217 Block time epoch = 1653320103
218 Contract name = Test
219 Contract address = 0x82592d5ae9E9ECc14b1740F330D3fAA00403a1F3
220 Trx name = storeNums
221 Trx args = {'_num0': 2, '_num1': 4, '_num2': 6}
222 Trx sender = 0x20e0A619E7Efb741a34b8EDC6251E2702e69bBDd
223 Trx value wei = 0
224 Trx hash = 0xb54e479495ea815943fa08069566c5cf68aaf70c6d42a23f7590bf399e0d6be1
225 Gas price wei = 20000000000
226 Gas used = 21484
227
228 >>> e=EventSearch(c, 'NumsStored')
229 >>> e.get_old()
230 []
Note
Line 2: Ether balance of
Test
contract.Line 4: Ether balance of the fourth Ganache account.
Line 5: Run
destroy()
. It takes one argument, the address of an account to receive all the Ether in the contract’s balance. Once you have destroyed a contract, you can no longer access its Ether. Save it now or lose it.Line 8: Contract has no Ether.
Line 10: Fourth account got it.
Line 11: If you try to get a public state variable’s value, you will get an error.
Line 19: If you try to call a function, you will get a slightly different error.
Line 30: Beware, if you try to run a transaction. It does not generate any error. For a destroyed contract, transactions will not be able to change any values on the blockchain. They look like they run, but they have no effect.
Line 31: The results do not show any event being emitted.
storeNums()
always emitsNumsStored()
.Line 46: Confirms that the
NumsStored()
event was not emitted. Because the contract is destroyed, the transaction did not alter the blockchain.
Send transactions / Get receipt
The trio of simpleth
methods described here are an alternative
to using run_trx()
. If you are happy with using run_trx,
you can skip this.
If you are curious, read on…
Ganache spoils us. It mines transactions immediately. You submit a transaction and can immediately get the results.
In a production application, running on a testnet or the mainnet, this is not the case. There is a delay between the time you submit a transaction and the time in which it is added to a block and mined. This delay could be a few seconds or many hours (or never).
You might chose to use
simpleth.Contract.send_trx()
with
simpleth.Contract.get_trx_receipt()
or
simpleth.Contract.get_trx_receipt_wait()
to give you more flexibility in managing the mining delay.
send_trx()
submits the transaction and
immediately returns the transaction hash.
The hash is a string that acts as the identifier of the
transaction.
Using that hash as a parameter, you can call get_trx_receipt()
to do a quick check to see if the transaction has finished. If not,
you can wait for some period time and check again. You would repeat this
until the transaction finishes or you give up. get_trx_receipt()
makes its check and returns immediately.
Alternatively, you can use the hash as a parameter
and call get_trx_receipt_wait()
. This does not return
immediately. It will periodically check to see if the transaction
has finished and returns when it has completed or
it times out before finding the transaction completed.
There are parameters for how frequently to poll and how long
to keep trying before timing out. Note that this call will
block until it returns.
Both get_trx_receipt()
and get_trx_receipt_wait()
return either None
if the transaction has not yet been
mined or transaction receipt
if the transaction completed.
Just like with run_trx()
, you can use the receipt to
get the Results
.
Relationship to run_trx()
Under the covers, run_trx()
simply makes a call to
send_trx()
and then a call to get_trx_receipt_wait()
.
You see that the parameters for run_trx()
are the union of
the parameters of send_trx()
and get_trx_receipt_wait()
.
run_trx()
blocks until the transaction completes or it times out.
run_trx()
only throws one exception of its own.
When you use run_trx()
most the exceptions
are thrown by send_trx()
or get_trx_receipt_wait()
.
Using Ganache with a mining delay
You can simulate a delay in completing a transaction. Ganache
setting’s allow you to change from the default of automine
,
where all transactions are mined immediately, to setting a constant number
of seconds before the transaction is put into a new block on the
chain. This allows you to, say, set a delay of ten seconds in
order to test use of the periodic checking in get_trx_receipt_wait()
or run_trx()
.
1 >>> trx_hash = c.submit_trx(user, 'storeNums', 1, 2, 3)
2 >>> receipt = c.get_trx_receipt(trx_hash)
3 >>> print(Results(c, receipt))
4 Block number = 7583
5 Block time epoch = 1653324228
6 Contract name = Test
7 Contract address = 0xe837B30EFA8Bd88De16276b6009a29ef70b1b693
8 Trx name = storeNums
9 Trx args = {'_num0': 1, '_num1': 2, '_num2': 3}
10 Trx sender = 0x20e0A619E7Efb741a34b8EDC6251E2702e69bBDd
11 Trx value wei = 0
12 Trx hash = 0xae9ac7ab7679b9f808e766153c5dd979fb78ed69cbed54c1e19ed9d0d5c8a881
13 Gas price wei = 20000000000
14 Gas used = 26164
15 Event name[0] = NumsStored
16 Event args[0] = {'timestamp': 1653324228, 'num0': 1, 'num1': 2, 'num2': 3}
17
18 >>> trx_hash = c.submit_trx(user, 'storeNums', 1, 2, 3)
19 >>> trx_hash
20 '0x0fe19c89b66c424c4696b2323b68dd72ef2de731709520cc5c24a78b927027a8'
21 >>> receipt = c.get_trx_receipt_wait(trx_hash, timeout=3600, poll_latency=15)
22 >>> print(Results(c, receipt))
23 Block number = 7584
24 Block time epoch = 1653324309
25 Contract name = Test
26 Contract address = 0xe837B30EFA8Bd88De16276b6009a29ef70b1b693
27 Trx name = storeNums
28 Trx args = {'_num0': 1, '_num1': 2, '_num2': 3}
29 Trx sender = 0x20e0A619E7Efb741a34b8EDC6251E2702e69bBDd
30 Trx value wei = 0
31 Trx hash = 0x0fe19c89b66c424c4696b2323b68dd72ef2de731709520cc5c24a78b927027a8
32 Gas price wei = 20000000000
33 Gas used = 26164
34 Event name[0] = NumsStored
35 Event args[0] = {'timestamp': 1653324309, 'num0': 1, 'num1': 2, 'num2': 3}
Note
Line 1: Instead of
run_trx
usesubmit_trx
to runstoreNums()
transaction. The transaction’s hash is returned.Line 2: Use that hash to get the transaction’s receipt.
Line 3: As before, we can get the results using that receipt.
Line 18: Sumit again.
Line 20: Here’s our hash.
Line 21: Use
get_trx_receipt_wait
this time and specify overrides to the defaults fortimeout
andpoll_latency
. Since my Ganache is not doing any mining delay these parameters do not come into play and this returns immediately with the receipt.Line 22: Get and print the results.
Compiling a contract
After creating a contract or making any code changes to an existing contract you need to compile it before it can be deployed on the blockchain.
You use the Solidity compiler, solc.exe
, to create two output files
and store them in the directory named in the environment variable,
SIMPLETH_ARTIFACTS_DIR
.
After a successful compile, the contract is ready to deploy.
Using solc
Use solc.exe
with the arguments shown below to make a contract
ready to deploy by simpleth
:
solc --abi --bin --optimize --overwrite -o <ARTIFACTS_DIR> <CONTRACT>
Where:
abi
writes the application binary interface filebin
writes the binary fileoptimize
enables the bytecode optimizer to create more efficient contracts.overwrite
replaces any existing copies of the fileso
specifies the path to the output directory for the files<ARTIFACTS_DIR>
is the path to the directory to hold the compiler output flies.<CONTRACT>
is the path to the Solidity smart contract source file to compile
1$ solc --abi --bin --optimize --overwrite -o %SIMPLETH_ARTIFACTS_DIR% contracts\Test.sol
2Compiler run successful. Artifact(s) can be found in directory "<%SIMPLETH_ARTIFACTS_DIR%>".
Note
Line 1 - the example runs from the
simpleth
directory.Line 2 - uses the environment variable for the path to the output directory
Line 3 - success message from compiler
See Solidity compiler documentation for compiler details.
Artifact directory
The artifact directory is crucial to simpleth
. It holds the information
about compiled contracts for the Contract
methods to use. The
environment variable, SIMPLETH_ARTIFACTS_DIR
, stores the path to the
directory.
There are up to five files for each contract stored in the artifact directory.
All files have <contract>
as the filename. The suffixes are explained
below:
Suffix |
Creator |
Purpose |
.abi |
solc.exe |
ABI file. Created with |
.addr |
Contract() |
Blockchain address. Written at deploy(). Required for |
.bin |
solc.exe |
BIN file. Created with |
.docdev |
solc.exe |
Developer Natspec comments JSON file. Created with |
.docuser |
solc.exe |
User Natspec comments JSON file. Created with |
1 $ cd %SIMPLETH_ARTIFACTS_DIR%
2 $ dir Test.*
3 ... snip ....
4
5 05/29/2022 09:06 AM 11,865 Test.abi
6 05/29/2022 08:02 AM 42 test.addr
7 05/29/2022 09:06 AM 12,100 Test.bin
Note
Line 8: Due to DOS file naming convention, upper and lower case does not matter in the file names. The .addr file will be present after a contract is deployed.
When deploy()
runs, it uses the environment variable to look in
that directory for the ABI and BIN files needed to install the contract
on the blockchain. deploy()
will write the address of the newly
installed contract to the <contract>.addr
file.
If a contract has been compiled, but not yet deployed, there will be
no .addr file in the directory. After the first-ever deployment the
.addr file is written to the directory. Any subsequent deploy()
will update the contract’s address stored in the file.
When connect()
runs, it uses that environment variable to
load up information about the deployed contract, including the address
of the deployed contract.
If you destroy an .addr file, that contract is lost to simpleth
.
You will not be able to access that installed instance of the contract.
Warning
If you do not set SIMPLETH_ARTIFACTS_DIR
, it will default to, .
,
the current working directory.