From 126a70755606649328d3b75accc775a0243b006c Mon Sep 17 00:00:00 2001 From: Mateusz Marcinkowski Date: Fri, 19 Jul 2024 11:08:42 +0200 Subject: [PATCH 1/5] feat: example web3py usage Signed-off-by: mateuszm-arianelabs --- tools/web3py-example/.env.example | 2 + tools/web3py-example/.gitignore | 1 + tools/web3py-example/README.md | 53 +++++++++ tools/web3py-example/contract/Greeter.sol | 18 +++ tools/web3py-example/scripts/test.py | 138 ++++++++++++++++++++++ 5 files changed, 212 insertions(+) create mode 100644 tools/web3py-example/.env.example create mode 100644 tools/web3py-example/.gitignore create mode 100644 tools/web3py-example/README.md create mode 100644 tools/web3py-example/contract/Greeter.sol create mode 100644 tools/web3py-example/scripts/test.py diff --git a/tools/web3py-example/.env.example b/tools/web3py-example/.env.example new file mode 100644 index 0000000000..a980be1bc6 --- /dev/null +++ b/tools/web3py-example/.env.example @@ -0,0 +1,2 @@ +OPERATOR_PRIVATE_KEY= +RELAY_ENDPOINT= diff --git a/tools/web3py-example/.gitignore b/tools/web3py-example/.gitignore new file mode 100644 index 0000000000..0c2ad0902b --- /dev/null +++ b/tools/web3py-example/.gitignore @@ -0,0 +1 @@ +.env diff --git a/tools/web3py-example/README.md b/tools/web3py-example/README.md new file mode 100644 index 0000000000..57508989af --- /dev/null +++ b/tools/web3py-example/README.md @@ -0,0 +1,53 @@ +# Web3py example +Example scripts for basic operations + +### How to start +1. **Install Solidity Compiler (`solc`)**: + - Install `solc` by following the instructions in the [Solidity documentation](https://docs.soliditylang.org/en/latest/installing-solidity.html). + - On Ubuntu, you can run: + ```sh + sudo apt install solc + ``` + +2. **Set up a clean environment (with virtual env)** + +```bash +# Install pip if it is not available: +$ which pip || curl https://bootstrap.pypa.io/get-pip.py | python + +# Install virtualenv if it is not available: +$ which virtualenv || pip install --upgrade virtualenv + +# *If* the above command displays an error, you can try installing as root: +$ sudo pip install virtualenv + +# Create a virtual environment: +$ virtualenv -p python3 ~/.venv-py3 + +# Activate your new virtual environment: +$ source ~/.venv-py3/bin/activate + +# With virtualenv active, make sure you have the latest packaging tools +$ pip install --upgrade pip setuptools + +# Now we can install web3.py... +$ pip install --upgrade web3 + +# Install python-dotenv +$ pip install python-dotenv + +# Install py-solc-x +$ pip install py-solc-x +``` + +Remember that each new terminal session requires you to reactivate your virtualenv, like: +```bash +$ source ~/.venv-py3/bin/activate +``` + +3. **Create and complete `.env` file from `.env.example`** + +4. **Run script** +```bash +python scripts/test.py +``` diff --git a/tools/web3py-example/contract/Greeter.sol b/tools/web3py-example/contract/Greeter.sol new file mode 100644 index 0000000000..73b4600bcd --- /dev/null +++ b/tools/web3py-example/contract/Greeter.sol @@ -0,0 +1,18 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity >0.5.0; + +contract Greeter { + string public greeting; + + constructor() public { + greeting = 'Hello'; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + } + + function greet() view public returns (string memory) { + return greeting; + } +} \ No newline at end of file diff --git a/tools/web3py-example/scripts/test.py b/tools/web3py-example/scripts/test.py new file mode 100644 index 0000000000..91ff1ac008 --- /dev/null +++ b/tools/web3py-example/scripts/test.py @@ -0,0 +1,138 @@ +# +# Hedera JSON RPC Relay - Web3py Example +# +# Copyright (C) 2022-2024 Hedera Hashgraph, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import unittest +from dotenv import load_dotenv +from web3 import Web3 +from solcx import install_solc, compile_source + +def setup_environment(): + # install latest solc + install_solc(version='latest') + + # load values from our .env file + load_dotenv() + OPERATOR_PRIVATE_KEY = os.getenv('OPERATOR_PRIVATE_KEY') + RELAY_ENDPOINT = os.getenv('RELAY_ENDPOINT') + + # connect to chain + w3 = Web3(Web3.HTTPProvider(RELAY_ENDPOINT)) + + # get account from pk + acc = w3.eth.account.from_key(OPERATOR_PRIVATE_KEY) + + return w3, acc + + +def get_balance(w3, acc): + balance = w3.eth.get_balance(acc.address) + return balance + + +def deploy_contract(w3, acc): + # read contract from file + with open('contract/Greeter.sol', 'r') as f: + source = f.read() + + # compile our Greeter contract + compiled_sol = compile_source(source, output_values=['abi', 'bin']) + + # retrieve the contract interface + contract_id, contract_interface = compiled_sol.popitem() + + bytecode = contract_interface['bin'] + abi = contract_interface['abi'] + + # create web3.py contract instance + Greeter = w3.eth.contract(abi=abi, bytecode=bytecode) + + # build transaction + unsent_tx_hash = Greeter.constructor().build_transaction({ + "from": acc.address, + "nonce": w3.eth.get_transaction_count(acc.address), + }) + + # sign transaction + signed_tx = w3.eth.account.sign_transaction(unsent_tx_hash, private_key=acc.key) + + # send transaction + tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) + tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) + + # create instance of deployed contract + greeter = w3.eth.contract( + address=tx_receipt.contractAddress, + abi=abi + ) + + return greeter, tx_receipt.contractAddress + + +def contract_view_call(greeter): + greeting = greeter.functions.greet().call() + return greeting + + +def contract_call(w3, acc, greeter): + # build contract call transaction + unsent_call_tx_hash = greeter.functions.setGreeting('Hello2').build_transaction({ + "from": acc.address, + "nonce": w3.eth.get_transaction_count(acc.address), + }) + + # sign transaction + signed_call_tx = w3.eth.account.sign_transaction(unsent_call_tx_hash, private_key=acc.key) + + # send transaction + call_tx_hash = w3.eth.send_raw_transaction(signed_call_tx.rawTransaction) + w3.eth.wait_for_transaction_receipt(call_tx_hash) + + # Verify the greeting has been updated + new_greeting = greeter.functions.greet().call() + return new_greeting + + +class TestGreeterContract(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.w3, cls.acc = setup_environment() + cls.greeter, cls.contract_address = deploy_contract(cls.w3, cls.acc) + + def test_get_balance(self): + balance = get_balance(self.w3, self.acc) + self.assertIsInstance(balance, int, "Account balance is an integer") + + def test_deploy_contract(self): + self.assertTrue(self.contract_address.startswith('0x'), "Contract address starts with '0x'") + + def test_call_view(self): + greeting = contract_view_call(self.greeter) + self.assertEqual(greeting, 'Hello', "Initial greeting matches expected value") + + def test_contract_call(self): + new_greeting = contract_call(self.w3, self.acc, self.greeter) + self.assertEqual(new_greeting, 'Hello2', "Updated greeting matches expected value") + + final_greeting = contract_view_call(self.greeter) + self.assertEqual(final_greeting, 'Hello2', "Final greeting matches expected value after update") + + +if __name__ == "__main__": + unittest.main() From cbddc63dbcac995c738a9f3755999e3d39f59d75 Mon Sep 17 00:00:00 2001 From: mateuszm-arianelabs Date: Wed, 24 Jul 2024 10:11:01 +0200 Subject: [PATCH 2/5] feat: change from compile_source to compile_files Signed-off-by: mateuszm-arianelabs --- tools/web3py-example/scripts/test.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tools/web3py-example/scripts/test.py b/tools/web3py-example/scripts/test.py index 91ff1ac008..d15736b733 100644 --- a/tools/web3py-example/scripts/test.py +++ b/tools/web3py-example/scripts/test.py @@ -20,7 +20,7 @@ import unittest from dotenv import load_dotenv from web3 import Web3 -from solcx import install_solc, compile_source +from solcx import install_solc, compile_files def setup_environment(): # install latest solc @@ -46,12 +46,8 @@ def get_balance(w3, acc): def deploy_contract(w3, acc): - # read contract from file - with open('contract/Greeter.sol', 'r') as f: - source = f.read() - # compile our Greeter contract - compiled_sol = compile_source(source, output_values=['abi', 'bin']) + compiled_sol = compile_files(['contract/Greeter.sol'], output_values=['abi', 'bin']) # retrieve the contract interface contract_id, contract_interface = compiled_sol.popitem() From cefca9bf6f20474490149adddcf29e84af1e66b9 Mon Sep 17 00:00:00 2001 From: mateuszm-arianelabs Date: Mon, 29 Jul 2024 15:48:17 +0200 Subject: [PATCH 3/5] feat: remove unnecessary solc install step in README, add comments with args and return types Signed-off-by: mateuszm-arianelabs --- tools/web3py-example/README.md | 13 ++------- tools/web3py-example/scripts/test.py | 43 ++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/tools/web3py-example/README.md b/tools/web3py-example/README.md index 57508989af..3a2921fa4c 100644 --- a/tools/web3py-example/README.md +++ b/tools/web3py-example/README.md @@ -2,14 +2,7 @@ Example scripts for basic operations ### How to start -1. **Install Solidity Compiler (`solc`)**: - - Install `solc` by following the instructions in the [Solidity documentation](https://docs.soliditylang.org/en/latest/installing-solidity.html). - - On Ubuntu, you can run: - ```sh - sudo apt install solc - ``` - -2. **Set up a clean environment (with virtual env)** +1. **Set up a clean environment (with virtual env)** ```bash # Install pip if it is not available: @@ -45,9 +38,9 @@ Remember that each new terminal session requires you to reactivate your virtuale $ source ~/.venv-py3/bin/activate ``` -3. **Create and complete `.env` file from `.env.example`** +2. **Create and complete `.env` file from `.env.example`** -4. **Run script** +3. **Run script** ```bash python scripts/test.py ``` diff --git a/tools/web3py-example/scripts/test.py b/tools/web3py-example/scripts/test.py index d15736b733..2729541924 100644 --- a/tools/web3py-example/scripts/test.py +++ b/tools/web3py-example/scripts/test.py @@ -23,8 +23,13 @@ from solcx import install_solc, compile_files def setup_environment(): - # install latest solc - install_solc(version='latest') + """ + Returns: + - w3: Initialized Web3 instance + - acc: Web3 account object + """ + # install solc + install_solc(version='0.8.24') # load values from our .env file load_dotenv() @@ -41,11 +46,27 @@ def setup_environment(): def get_balance(w3, acc): + """ + Args: + - w3: Initialized Web3 instance + - acc: Web3 account object + + Returns: + - Account balance in wei + """ balance = w3.eth.get_balance(acc.address) return balance def deploy_contract(w3, acc): + """ + Args: + - w3: Initialized Web3 instance + - acc: Web3 account object + + Returns: + - tuple: (Deployed contract instance, Contract address) + """ # compile our Greeter contract compiled_sol = compile_files(['contract/Greeter.sol'], output_values=['abi', 'bin']) @@ -81,11 +102,27 @@ def deploy_contract(w3, acc): def contract_view_call(greeter): + """ + Args: + - greeter: Deployed Greeter contract instance + + Returns: + - Current greeting message + """ greeting = greeter.functions.greet().call() return greeting def contract_call(w3, acc, greeter): + """ + Args: + - w3: Initialized Web3 instance + - acc: Web3 account object + - greeter: Deployed Greeter contract instance + + Returns: + - Updated greeting message + """ # build contract call transaction unsent_call_tx_hash = greeter.functions.setGreeting('Hello2').build_transaction({ "from": acc.address, @@ -131,4 +168,4 @@ def test_contract_call(self): if __name__ == "__main__": - unittest.main() + unittest.main() \ No newline at end of file From bad6853c50705cd7f8146264df7a575bf93f3ad1 Mon Sep 17 00:00:00 2001 From: mateuszm-arianelabs Date: Wed, 7 Aug 2024 13:29:37 +0200 Subject: [PATCH 4/5] feat: Add optimize to compilation, new lines in test.py, Greeter.sol Signed-off-by: mateuszm-arianelabs --- tools/web3py-example/contract/Greeter.sol | 2 +- tools/web3py-example/scripts/test.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/web3py-example/contract/Greeter.sol b/tools/web3py-example/contract/Greeter.sol index 73b4600bcd..060d1eb530 100644 --- a/tools/web3py-example/contract/Greeter.sol +++ b/tools/web3py-example/contract/Greeter.sol @@ -15,4 +15,4 @@ contract Greeter { function greet() view public returns (string memory) { return greeting; } -} \ No newline at end of file +} diff --git a/tools/web3py-example/scripts/test.py b/tools/web3py-example/scripts/test.py index 2729541924..a4910fae16 100644 --- a/tools/web3py-example/scripts/test.py +++ b/tools/web3py-example/scripts/test.py @@ -68,7 +68,7 @@ def deploy_contract(w3, acc): - tuple: (Deployed contract instance, Contract address) """ # compile our Greeter contract - compiled_sol = compile_files(['contract/Greeter.sol'], output_values=['abi', 'bin']) + compiled_sol = compile_files(['contract/Greeter.sol'], output_values=['abi', 'bin'], optimize=True) # retrieve the contract interface contract_id, contract_interface = compiled_sol.popitem() @@ -168,4 +168,4 @@ def test_contract_call(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() From cadd75009fb8fe4f2433e8291638286bb834612f Mon Sep 17 00:00:00 2001 From: mateuszm-arianelabs Date: Wed, 14 Aug 2024 14:47:48 +0200 Subject: [PATCH 5/5] fix: License typo changes Signed-off-by: mateuszm-arianelabs --- tools/web3py-example/contract/Greeter.sol | 2 +- tools/web3py-example/scripts/test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/web3py-example/contract/Greeter.sol b/tools/web3py-example/contract/Greeter.sol index 060d1eb530..235f168134 100644 --- a/tools/web3py-example/contract/Greeter.sol +++ b/tools/web3py-example/contract/Greeter.sol @@ -1,4 +1,4 @@ -//SPDX-License-Identifier: Unlicense +//SPDX-License-Identifier: Apache-2.0 pragma solidity >0.5.0; contract Greeter { diff --git a/tools/web3py-example/scripts/test.py b/tools/web3py-example/scripts/test.py index a4910fae16..93feccaa9f 100644 --- a/tools/web3py-example/scripts/test.py +++ b/tools/web3py-example/scripts/test.py @@ -1,5 +1,5 @@ # -# Hedera JSON RPC Relay - Web3py Example +# Hedera JSON RPC Relay # # Copyright (C) 2022-2024 Hedera Hashgraph, LLC #