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.

_images/section_separator.png

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

_images/section_separator.png

Deploy

simpleth.Contract.deploy() will add a contract to the blockchain. After a contract is deployed, it is ready for use.

Deploy the Test contract
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 our simpleth.Blockchain object for the examples.

  • Line 3: Assign the blockchain account simpleth.Blockchain.address() for the first Ganache account to owner .

  • Line 4: c is our simpleth.Contract object for the Test contract to be used in all examples.

  • Line 6: deploy adds the Test 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 Python owner was used to deploy, it becomes the Solidity owner. This becomes important later. There are examples where only the Solidity contract owner can run a transaction.

    deploy returns the transaction receipt. To avoid having this print out, I stored it in receipt. 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.

_images/section_separator.png

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.

_images/section_separator.png

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 of nums 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 .

_images/section_separator.png

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.)

_images/section_separator.png

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.

_images/section_separator.png

Search for events

simpleth.EventSearch has two methods to find and retrieve the information from events emitted by transactions:

  1. simpleth.EventSearch.get_old() returns event info from a specified range of previously mined blocks

  2. simpleth.EventSearch.get_new() returns event info from newly mined blocks.

Get old events
 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 asks get_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.

Get new events
 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 new NumsStored 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:

Python program (event_poll.py) to watch for events
 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:

  1. Python interpreter where transactions were run

  2. 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:

Interpreter session while event_poll.py runs
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:

Running event_poll.py
 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.

Getting the events emitted while event_poll.py ran
 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 for get_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 to event_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 by event_poll.py in the fifth three-second check.

And so on.

_images/section_separator.png

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.

Get old and new events with event args
 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.

_images/section_separator.png

Transaction results

simpleth.Results can be used after a transaction completes to see the details about it.

Get the results of a transaction
 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 the storeNums 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 .

_images/section_separator.png

Handling Ether

simpleth has a handful of methods and properties for handling Ether:

  1. simpleth.Convert.denominations_to_wei() returns Ether denominations and values.

  2. simpleth.Convert.convert_ether() to convert amount from one denomination to another.

  3. simpleth.Blockchain.balance_of() returns the Ether balance_of, in wei , for a specified address.

  4. simpleth.Blockchain.send_ether() transfers the specified amount of Ether, in wei , from one address to another.

  5. simpleth.Contract.run_trx() has an optional parameter, value_wei which will send the specified amount of Ether, in wei , to the transaction.

Methods and properties to handle ether
  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 a decimal 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 to user.

  • 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 as payable 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 a payable fallback function in order to receive ether. The Test contract has such a function as the final function in the contract.

_images/section_separator.png

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.

Test.sol bytes-related code
 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.

Get initial values
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.

Set Solidity bytes with a string of characters
 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.

Set Solidity bytes with an integer value
 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.

Store eight hex characters as four bytes
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.

Use bytearray as args
 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.

Exception when attempting to store eight bytes into four
 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

Convert string of hex to a Python byte string and back
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

Convert string to hex and back to string
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

Convert returned bytes value to an 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.

Append nulls to match fixed byte array length
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')
_images/section_separator.png

Handling time

simpleth provides support for handing time, especially epoch time:

  1. simpleth.Convert.epoch_time() returns the current time in epoch seconds.

  2. simpleth.Convert.local_time_string() returns the current time as a string.

  3. simpleth.Convert.to_local_time_string() converts epoch seconds to a time string.

Handling time
 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’s block.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.

_images/section_separator.png

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.

Getting a SimplethError in the Python interpreter
 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.

Handling a SimplethError
 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 the Contract 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.

Properties of a SimplethError
 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 a require() that failed or a revert(). 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.

_images/section_separator.png

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:

  1. Use the Solidity operation, require() , to validate a condition is met. If the condition is not met, a revert() is done and an optional message string will be available in the SimplethError

    require() is commonly used in a contract modifier and a frequent type of modifier is to limit access to a transaction to one or more specified accounts.

  2. 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.

  3. 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:

Handling transaction error exceptions
  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 into nums[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.

Handling transaction thrown exceptions - require and its message
 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’s require() 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, which SimplethError catches.

  • Line 26: Uses a try / except to get the message from the failed require().

  • 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()

Handling transaction thrown exceptions - modifier with message
 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 our user account does not own Test .

  • Line 4: Shows how to obtain the message coded in the contract for the require() used in the modifier .

  • 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.

Handling transaction thrown exceptions - assert
 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 the ValueError 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():

Handling transaction thrown exceptions - revert with message
 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.

_images/section_separator.png

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:

Destroying Test with a selfdestruct
  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 emits NumsStored().

  • Line 46: Confirms that the NumsStored() event was not emitted. Because the contract is destroyed, the transaction did not alter the blockchain.

_images/section_separator.png

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().

Send transaction and get the receipt
 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 use submit_trx to run storeNums() 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 for timeout and poll_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.

_images/section_separator.png

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:

Command to compile a smart contract for use by simpleth
solc --abi --bin --optimize --overwrite -o <ARTIFACTS_DIR> <CONTRACT>

Where:

  • abi writes the application binary interface file

  • bin writes the binary file

  • optimize enables the bytecode optimizer to create more efficient contracts.

  • overwrite replaces any existing copies of the files

  • o 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

Example of compiling the Test.sol contract
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 --abi arg. Must be present to deploy() or connect().

.addr

Contract()

Blockchain address. Written at deploy(). Required for connect().

.bin

solc.exe

BIN file. Created with --bin arg. Must be present to deploy() or connect().

.docdev

solc.exe

Developer Natspec comments JSON file. Created with --docdev arg. Used for documentation.

.docuser

solc.exe

User Natspec comments JSON file. Created with --docuser arg. Used for documentation.

Example of Test contract files in the artifact directory
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.