Sarah Azouvi commited on
Commit
37f012e
·
1 Parent(s): 68e14c4

added smart contract

Browse files
Files changed (2) hide show
  1. app.py +261 -2
  2. requirements.txt +2 -0
app.py CHANGED
@@ -14,6 +14,9 @@ from web3 import Web3
14
  from eth_tester import EthereumTester
15
  from web3.providers.eth_tester import EthereumTesterProvider
16
  from eth_account import Account
 
 
 
17
 
18
  # Configure logging
19
  logging.basicConfig(level=logging.INFO)
@@ -40,6 +43,14 @@ class EthereumClient:
40
 
41
  # Initialize with local testnet by default
42
  self._connect_local_testnet()
 
 
 
 
 
 
 
 
43
 
44
  def _connect_local_testnet(self):
45
  """Connect to local test blockchain"""
@@ -179,6 +190,116 @@ class EthereumClient:
179
  except Exception as e:
180
  logger.error(f"Error funding test account: {e}")
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  # Initialize the Ethereum client
183
  config = EthereumConfig(use_testnet=True)
184
  eth_client = EthereumClient(config)
@@ -343,7 +464,7 @@ def send_ethereum_transaction(from_account: str, to_address: str, amount_eth: fl
343
  }
344
 
345
  signed_txn = eth_client.w3.eth.account.sign_transaction(transaction, private_key)
346
- tx_hash = eth_client.w3.eth.send_raw_transaction(signed_txn.rawTransaction)
347
 
348
  return {
349
  "success": True,
@@ -486,6 +607,23 @@ def mine_ethereum_blocks(num_blocks: int = 1) -> dict:
486
  except Exception as e:
487
  return {"success": False, "error": str(e)}
488
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  # Gradio Interface Functions
490
  def switch_network_ui(network: str, api_key: str):
491
  """Gradio wrapper for network switching"""
@@ -553,6 +691,55 @@ def mine_blocks_ui(num_blocks: int):
553
  result = mine_ethereum_blocks(num_blocks)
554
  return json.dumps(result, indent=2)
555
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
556
  # Create Gradio Interface
557
  def create_gradio_app():
558
  """Create the Gradio web interface"""
@@ -705,6 +892,78 @@ def create_gradio_app():
705
  fn=list_ethereum_accounts,
706
  outputs=accounts_json_output
707
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
708
 
709
  gr.Markdown("---")
710
  gr.Markdown("### 🔒 Safety Features for Public Deployment")
@@ -714,7 +973,7 @@ def create_gradio_app():
714
  gr.Markdown("- **Transaction Restrictions**: Transactions only available on safe local testnet")
715
 
716
  gr.Markdown("### 🤖 MCP Integration")
717
- gr.Markdown("**Available MCP Tools**: `switch_ethereum_network`, `get_network_info`, `create_ethereum_account`, `get_ethereum_balance`, `send_ethereum_transaction`, `get_latest_ethereum_block`, `get_transaction_info`, `list_ethereum_accounts`, `mine_ethereum_blocks`")
718
 
719
  return app
720
 
 
14
  from eth_tester import EthereumTester
15
  from web3.providers.eth_tester import EthereumTesterProvider
16
  from eth_account import Account
17
+ from solcx import compile_source, install_solc
18
+ import solcx
19
+ from eth_utils import to_checksum_address
20
 
21
  # Configure logging
22
  logging.basicConfig(level=logging.INFO)
 
43
 
44
  # Initialize with local testnet by default
45
  self._connect_local_testnet()
46
+
47
+ # Install Solidity compiler for contract compilation
48
+ try:
49
+ install_solc('0.8.19')
50
+ solcx.set_solc_version('0.8.19')
51
+ except Exception as e:
52
+ logger.warning(f"Could not install Solidity compiler: {e}")
53
+
54
 
55
  def _connect_local_testnet(self):
56
  """Connect to local test blockchain"""
 
190
  except Exception as e:
191
  logger.error(f"Error funding test account: {e}")
192
 
193
+ def compile_and_deploy_contract(self, source_code: str, contract_name: str, from_account: str, constructor_args: list = None) -> dict:
194
+ """
195
+ Compile and deploy a smart contract.
196
+ Only works on local testnet for safety.
197
+
198
+ Args:
199
+ source_code (str): Solidity source code
200
+ contract_name (str): Name of the contract to deploy
201
+ from_account (str): Account name to deploy from
202
+ constructor_args (list): Constructor arguments if any
203
+
204
+ Returns:
205
+ dict: Deployment result
206
+ """
207
+ try:
208
+ if self.is_mainnet_readonly:
209
+ return {
210
+ "success": False,
211
+ "error": "Contract deployment disabled on mainnet for safety. Switch to testnet."
212
+ }
213
+
214
+ if self.current_network != "local_testnet":
215
+ return {
216
+ "success": False,
217
+ "error": "Contract deployment only available on local testnet for safety in public deployment"
218
+ }
219
+
220
+ if from_account not in self.accounts:
221
+ return {"success": False, "error": "Account not found"}
222
+
223
+ # Compile the contract
224
+ try:
225
+ compiled_sol = compile_source(source_code)
226
+ contract_interface = None
227
+
228
+ # Find the contract in compiled output
229
+ for contract_id, contract_data in compiled_sol.items():
230
+ if contract_name in contract_id:
231
+ contract_interface = contract_data
232
+ break
233
+
234
+ if not contract_interface:
235
+ return {"success": False, "error": f"Contract '{contract_name}' not found in source code"}
236
+
237
+ except Exception as e:
238
+ return {"success": False, "error": f"Compilation failed: {str(e)}"}
239
+
240
+ # Get account details
241
+ account_data = self.accounts[from_account]
242
+ private_key = account_data["private_key"]
243
+ from_address = account_data["address"]
244
+
245
+ # Create contract instance
246
+ contract = self.w3.eth.contract(
247
+ abi=contract_interface['abi'],
248
+ bytecode=contract_interface['bin']
249
+ )
250
+
251
+ # Prepare constructor arguments
252
+ constructor_args = constructor_args or []
253
+
254
+ # Build deployment transaction
255
+ nonce = self.w3.eth.get_transaction_count(from_address)
256
+
257
+ # Estimate gas for deployment
258
+ try:
259
+ gas_estimate = contract.constructor(*constructor_args).estimate_gas({
260
+ 'from': from_address
261
+ })
262
+ # Add 20% buffer to gas estimate
263
+ gas_limit = int(gas_estimate * 1.2)
264
+ except Exception as e:
265
+ gas_limit = 3000000 # Default gas limit
266
+
267
+ # Build and sign transaction
268
+ transaction = contract.constructor(*constructor_args).build_transaction({
269
+ 'from': from_address,
270
+ 'gas': gas_limit,
271
+ 'gasPrice': self.w3.eth.gas_price,
272
+ 'nonce': nonce,
273
+ })
274
+
275
+ signed_txn = self.w3.eth.account.sign_transaction(transaction, private_key)
276
+ tx_hash = self.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
277
+
278
+ # Wait for transaction receipt
279
+ tx_receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)
280
+
281
+ if tx_receipt.status == 1:
282
+ return {
283
+ "success": True,
284
+ "contract_address": tx_receipt.contractAddress,
285
+ "transaction_hash": tx_hash.hex(),
286
+ "gas_used": tx_receipt.gasUsed,
287
+ "contract_name": contract_name,
288
+ "deployed_by": from_address,
289
+ "network": self.current_network,
290
+ "abi": contract_interface['abi']
291
+ }
292
+ else:
293
+ return {
294
+ "success": False,
295
+ "error": "Contract deployment failed",
296
+ "transaction_hash": tx_hash.hex()
297
+ }
298
+
299
+ except Exception as e:
300
+ return {"success": False, "error": str(e)}
301
+
302
+
303
  # Initialize the Ethereum client
304
  config = EthereumConfig(use_testnet=True)
305
  eth_client = EthereumClient(config)
 
464
  }
465
 
466
  signed_txn = eth_client.w3.eth.account.sign_transaction(transaction, private_key)
467
+ tx_hash = eth_client.w3.eth.send_raw_transaction(signed_txn.raw_transaction)
468
 
469
  return {
470
  "success": True,
 
607
  except Exception as e:
608
  return {"success": False, "error": str(e)}
609
 
610
+ def deploy_smart_contract(source_code: str, contract_name: str, from_account: str, constructor_args: list = None) -> dict:
611
+ """
612
+ Compile and deploy a smart contract to the blockchain.
613
+ Note: Only works on local testnet for safety.
614
+
615
+ Args:
616
+ source_code (str): Solidity source code
617
+ contract_name (str): Name of the contract to deploy
618
+ from_account (str): Name of the account to deploy from
619
+ constructor_args (list): Constructor arguments if any
620
+
621
+ Returns:
622
+ dict: Contract deployment result
623
+ """
624
+ return eth_client.compile_and_deploy_contract(source_code, contract_name, from_account, constructor_args)
625
+
626
+
627
  # Gradio Interface Functions
628
  def switch_network_ui(network: str, api_key: str):
629
  """Gradio wrapper for network switching"""
 
691
  result = mine_ethereum_blocks(num_blocks)
692
  return json.dumps(result, indent=2)
693
 
694
+ def deploy_contract_ui(source_code: str, contract_name: str, from_account: str, constructor_args_str: str):
695
+ """Gradio wrapper for contract deployment"""
696
+ if not source_code or not contract_name or not from_account:
697
+ return "Please provide source code, contract name, and from account"
698
+
699
+ # Parse constructor arguments if provided
700
+ constructor_args = []
701
+ if constructor_args_str.strip():
702
+ try:
703
+ # Parse constructor arguments with type conversion
704
+ args_list = [arg.strip() for arg in constructor_args_str.split(',') if arg.strip()]
705
+
706
+ for arg in args_list:
707
+ # Try to convert to appropriate types
708
+ if arg.lower() in ['true', 'false']:
709
+ # Boolean
710
+ constructor_args.append(arg.lower() == 'true')
711
+ elif arg.startswith('0x') and len(arg) == 42:
712
+ # Ethereum address
713
+ constructor_args.append(to_checksum_address(arg))
714
+ elif arg.startswith('"') and arg.endswith('"'):
715
+ # String (remove quotes)
716
+ constructor_args.append(arg[1:-1])
717
+ elif arg.startswith("'") and arg.endswith("'"):
718
+ # String (remove quotes)
719
+ constructor_args.append(arg[1:-1])
720
+ elif '.' in arg:
721
+ # Float/Decimal - convert to int if it's actually a whole number
722
+ try:
723
+ float_val = float(arg)
724
+ if float_val.is_integer():
725
+ constructor_args.append(int(float_val))
726
+ else:
727
+ constructor_args.append(float_val)
728
+ except ValueError:
729
+ constructor_args.append(arg) # Keep as string if conversion fails
730
+ else:
731
+ # Try integer first, then keep as string
732
+ try:
733
+ constructor_args.append(int(arg))
734
+ except ValueError:
735
+ constructor_args.append(arg)
736
+
737
+ except Exception as e:
738
+ return f"Error parsing constructor arguments: {str(e)}"
739
+
740
+ result = deploy_smart_contract(source_code, contract_name, from_account, constructor_args)
741
+ return json.dumps(result, indent=2)
742
+
743
  # Create Gradio Interface
744
  def create_gradio_app():
745
  """Create the Gradio web interface"""
 
892
  fn=list_ethereum_accounts,
893
  outputs=accounts_json_output
894
  )
895
+ with gr.Tab("📄 Smart Contracts"):
896
+ gr.Markdown("### Deploy Smart Contracts")
897
+ gr.Markdown("*⚠️ Only available on local testnet for safety*")
898
+
899
+ with gr.Row():
900
+ with gr.Column():
901
+ gr.Markdown("#### Contract Deployment")
902
+ contract_source = gr.Textbox(
903
+ label="Solidity Source Code",
904
+ placeholder="""pragma solidity ^0.8.19;
905
+
906
+ contract SimpleStorage {
907
+ uint256 public storedData;
908
+
909
+ constructor(uint256 _initialValue) {
910
+ storedData = _initialValue;
911
+ }
912
+
913
+ function set(uint256 _value) public {
914
+ storedData = _value;
915
+ }
916
+
917
+ function get() public view returns (uint256) {
918
+ return storedData;
919
+ }
920
+ }""",
921
+ lines=15
922
+ )
923
+
924
+ with gr.Row():
925
+ contract_name_input = gr.Textbox(
926
+ label="Contract Name",
927
+ placeholder="SimpleStorage"
928
+ )
929
+ deploy_from_account = gr.Textbox(
930
+ label="Deploy From Account",
931
+ placeholder="test_account_1"
932
+ )
933
+
934
+ constructor_args_input = gr.Textbox(
935
+ label="Constructor Arguments (comma-separated)",
936
+ placeholder="100",
937
+ info="Examples: 100 (number), \"Hello\" (string), 0x1234...abcd (address), true/false (boolean)"
938
+ )
939
+
940
+ deploy_btn = gr.Button("Deploy Contract", variant="primary")
941
+ deploy_output = gr.Textbox(label="Deployment Result", lines=12)
942
+
943
+ deploy_btn.click(
944
+ deploy_contract_ui,
945
+ inputs=[contract_source, contract_name_input, deploy_from_account, constructor_args_input],
946
+ outputs=[deploy_output]
947
+ )
948
+
949
+ with gr.Column():
950
+ gr.Markdown("#### Example Contracts")
951
+ gr.Markdown("""
952
+ **Simple Storage Contract:**
953
+ ```solidity
954
+ pragma solidity ^0.8.19;
955
+
956
+ contract SimpleStorage {
957
+ uint256 public storedData;
958
+
959
+ constructor(uint256 _initialValue) {
960
+ storedData = _initialValue;
961
+ }
962
+
963
+ function set(uint256 _value) public {
964
+ storedData = _value;
965
+ }
966
+ }""")
967
 
968
  gr.Markdown("---")
969
  gr.Markdown("### 🔒 Safety Features for Public Deployment")
 
973
  gr.Markdown("- **Transaction Restrictions**: Transactions only available on safe local testnet")
974
 
975
  gr.Markdown("### 🤖 MCP Integration")
976
+ gr.Markdown("**Available MCP Tools**: `switch_ethereum_network`, `get_network_info`, `create_ethereum_account`, `get_ethereum_balance`, `send_ethereum_transaction`, `get_latest_ethereum_block`, `get_transaction_info`, `list_ethereum_accounts`, `mine_ethereum_blocks`, `deploy_smart_contract`")
977
 
978
  return app
979
 
requirements.txt CHANGED
@@ -9,3 +9,5 @@ web3>=6.15.0
9
  eth-account>=0.10.0
10
  eth-tester
11
  py-evm
 
 
 
9
  eth-account>=0.10.0
10
  eth-tester
11
  py-evm
12
+ py-solc-x
13
+ eth-utils