def _connect_libraries(self): zk_print_banner(f'Deploying Libraries') sender = self.w3.eth.accounts[0] # Since eth-tester is not persistent -> always automatically deploy libraries with cfg.library_compilation_environment(): with tempfile.TemporaryDirectory() as tmpdir: with log_context('transaction', 'deploy_pki'): pki_sol = save_to_file( tmpdir, f'{cfg.pki_contract_name}.sol', library_contracts.get_pki_contract()) self._pki_contract = self._deploy_contract( sender, self.compile_contract(pki_sol, cfg.pki_contract_name)) zk_print( f'Deployed pki contract at address "{self.pki_contract.address}"' ) with log_context('transaction', 'deploy_verify_libs'): verify_sol = save_to_file( tmpdir, 'verify_libs.sol', library_contracts.get_verify_libs_code()) self._lib_addresses = {} for lib in cfg.external_crypto_lib_names: out = self._deploy_contract( sender, self.compile_contract(verify_sol, lib)) self._lib_addresses[lib] = out.address zk_print( f'Deployed crypto lib {lib} at address "{out.address}"' )
def _connect_libraries(self): if not cfg.blockchain_pki_address: raise BlockChainError('Must specify pki address in config.') lib_addresses = [] if cfg.external_crypto_lib_names: lib_addresses = [addr.strip() for addr in cfg.blockchain_crypto_lib_addresses.split(',')] if cfg.blockchain_crypto_lib_addresses else [] if len(lib_addresses) != len(cfg.external_crypto_lib_names): raise BlockChainError('Must specify all crypto library addresses in config\n' f'Expected {len(cfg.external_crypto_lib_names)} was {len(lib_addresses)}') with cfg.library_compilation_environment(): with tempfile.TemporaryDirectory() as tmpdir: for crypto_params in cfg.all_crypto_params(): contract_name = cfg.get_pki_contract_name(crypto_params.crypto_name) pki_sol = save_to_file(tmpdir, f'{contract_name}.sol', library_contracts.get_pki_contract(crypto_params)) self._pki_contract = self._verify_contract_integrity(cfg.blockchain_pki_address, pki_sol, contract_name=contract_name) verify_sol = save_to_file(tmpdir, 'verify_libs.sol', library_contracts.get_verify_libs_code()) self._lib_addresses = {} for lib, addr in zip(cfg.external_crypto_lib_names, lib_addresses): out = self._verify_contract_integrity(addr, verify_sol, contract_name=lib, is_library=True) self._lib_addresses[lib] = out.address
def main(): # parse arguments a = parse_arguments() from zkay.config_version import Versions if a.cmd == 'version': print(Versions.ZKAY_VERSION) return if a.cmd == 'update-solc': try: import solcx solcx.install_solc_pragma( Versions.ZKAY_SOLC_VERSION_COMPATIBILITY.expression) except Exception as e: print(f'ERROR: Error while updating solc\n{e}') return from pathlib import Path import zkay.zkay_frontend as frontend from zkay import my_logging from zkay.config import cfg from zkay.utils.helpers import read_file, save_to_file from zkay.errors.exceptions import ZkayCompilerError from zkay.my_logging.log_context import log_context from zkay.utils.progress_printer import fail_print, success_print from zkay.zkay_ast.process_ast import get_processed_ast, get_parsed_ast_and_fake_code # Load configuration files try: cfg.load_configuration_from_disk(a.config_file) except Exception as e: with fail_print(): print(f"ERROR: Failed to load configuration files\n{e}") exit(42) # Support for overriding any user config setting via command line # The evaluation order for configuration loading is: # Default values in config.py -> Site config.json -> user config.json -> local config.json -> cmdline arguments # Settings defined at a later stage override setting values defined at an earlier stage override_dict = {} for name in vars(UserConfig): if name[0] != '_' and hasattr(a, name): val = getattr(a, name) if val is not None: override_dict[name] = val cfg.override_defaults(override_dict) if a.cmd in ['deploy-pki', 'deploy-crypto-libs']: import tempfile from zkay.compiler.privacy import library_contracts from zkay.transaction.runtime import Runtime with tempfile.TemporaryDirectory() as tmpdir: try: with cfg.library_compilation_environment(): if a.cmd == 'deploy-pki': file = save_to_file( tmpdir, f'{cfg.pki_contract_name}.sol', library_contracts.get_pki_contract()) addr = Runtime.blockchain().deploy_solidity_contract( file, cfg.pki_contract_name, a.account) print(f'Deployed pki contract at: {addr}') else: if not cfg.external_crypto_lib_names: print( 'Current proving scheme does not require library deployment' ) else: file = save_to_file( tmpdir, 'verify_libs.sol', library_contracts.get_verify_libs_code()) for lib in cfg.external_crypto_lib_names: addr = Runtime.blockchain( ).deploy_solidity_contract( file, lib, a.account) print( f'Deployed crypto library {lib} at: {addr}' ) except Exception as e: with fail_print(): print(f"ERROR: Deployment failed\n{e}") else: # Solc version override if hasattr(a, 'solc_version') and a.solc_version is not None: try: cfg.override_solc(a.solc_version) except ValueError as e: with fail_print(): print(f'Error: {e}') exit(10) print(f'Using solc version {Versions.SOLC_VERSION}') input_path = Path(a.input) if not input_path.exists(): with fail_print(): print(f'Error: input file \'{input_path}\' does not exist') exit(1) if a.cmd == 'check': # only type-check print(f'Type checking file {input_path.name}:') code = read_file(str(input_path)) try: get_processed_ast(code) except ZkayCompilerError as e: with fail_print(): print(f'{e}') exit(3) elif a.cmd == 'solify': was_unit_test = cfg.is_unit_test cfg._is_unit_test = True # Suppress other output try: _, fake_code = get_parsed_ast_and_fake_code( read_file(str(input_path))) print(fake_code) except ZkayCompilerError as e: with fail_print(): print(f'{e}') exit(3) finally: cfg._is_unit_test = was_unit_test exit(0) elif a.cmd == 'compile': # create output directory output_dir = Path(a.output).absolute() if not output_dir.exists(): os.makedirs(output_dir) elif not output_dir.is_dir(): with fail_print(): print(f'Error: \'{output_dir}\' is not a directory') exit(2) # Enable logging if a.log: log_file = my_logging.get_log_file(filename='compile', include_timestamp=False, label=None) my_logging.prepare_logger(log_file) # only type-check print(f'Compiling file {input_path.name}:') # compile with log_context('inputfile', os.path.basename(a.input)): try: frontend.compile_zkay_file(str(input_path), str(output_dir)) except ZkayCompilerError as e: with fail_print(): print(f'{e}') exit(3) elif a.cmd == 'import': # create output directory output_dir = Path(a.output).absolute() if output_dir.exists(): with fail_print(): print(f'Error: \'{output_dir}\' already exists') exit(2) try: frontend.extract_zkay_package(str(input_path), str(output_dir)) except ZkayCompilerError as e: with fail_print(): print( f"ERROR while compiling unpacked zkay contract.\n{e}") exit(3) except Exception as e: with fail_print(): print(f"ERROR while unpacking zkay contract\n{e}") exit(5) elif a.cmd == 'export': output_filename = Path(a.output).absolute() os.makedirs(output_filename.parent, exist_ok=True) try: frontend.package_zkay_contract(str(input_path), str(output_filename)) except Exception as e: with fail_print(): print(f"ERROR while exporting zkay contract\n{e}") exit(4) elif a.cmd in ['run', 'deploy', 'connect']: from enum import IntEnum from zkay.transaction.offchain import ContractSimulator def echo_only_simple_expressions(e): if isinstance(e, (bool, int, str, list, tuple, IntEnum)): print(e) # Enable logging if a.log: log_file = my_logging.get_log_file( filename=f'transactions_{input_path.name}', include_timestamp=True, label=None) my_logging.prepare_logger(log_file) contract_dir = str(input_path.absolute()) frontend.use_configuration_from_manifest(contract_dir) if a.account is not None: me = a.account else: me = ContractSimulator.default_address() if me is not None: me = me.val import code import sys if a.cmd == 'run': # Dynamically load module and replace globals with module globals contract_mod = frontend.load_transaction_interface_from_directory( contract_dir) contract_mod.me = me sys.displayhook = echo_only_simple_expressions code.interact(local=contract_mod.__dict__) else: cmod = frontend.load_transaction_interface_from_directory( contract_dir) c = frontend.load_contract_transaction_interface_from_module( cmod) if a.cmd == 'deploy': from ast import literal_eval cargs = a.constructor_args args = [] for arg in cargs: try: val = literal_eval(arg) except Exception: val = arg args.append(val) try: c.deploy(*args, user=me, project_dir=contract_dir) except (ValueError, TypeError) as e: with fail_print(): print( f'ERROR invalid arguments.\n{e}\n\nExpected contructor signature: ', end='') if hasattr(c, 'constructor'): from inspect import signature sig = str(signature(c.constructor)) sig = sig[5:] if not sig[5:].startswith( ",") else sig[7:] # without self print(f'({sig}') else: print('()') exit(11) except Exception as e: with fail_print(): print(f'ERROR: failed to deploy contract\n{e}') exit(12) elif a.cmd == 'connect': try: c_inst = c.connect(address=a.address, user=me, project_dir=contract_dir) except Exception as e: with fail_print(): print(f'ERROR: failed to connect to contract\n{e}') exit(13) # Open interactive shell in context of contract object import inspect contract_scope = {name: getattr(c_inst, name) for name in dir(c_inst) if inspect.isclass(getattr(c_inst, name)) \ or (name != 'constructor' and hasattr(getattr(c_inst, name), '_can_be_external') and getattr(c_inst, name)._can_be_external) or name in ['state', 'api']} contract_scope['me'] = me contract_scope['help'] = lambda o=None: help( o ) if o is not None else ContractSimulator.reduced_help(c) sys.displayhook = echo_only_simple_expressions code.interact(local=contract_scope) else: raise ValueError(f'unexpected command {a.cmd}') exit(0) else: raise NotImplementedError(a.cmd) with success_print(): print("Finished successfully")
def compile_zkay(code: str, output_dir: str, import_keys: bool = False, **kwargs) -> Tuple[CircuitGenerator, str]: """ Parse, type-check and compile the given zkay code. Note: If a SolcException is raised, this indicates a bug in zkay (i.e. zkay produced solidity code which doesn't compile, without raising a ZkayCompilerError) :param code: zkay code to compile :param output_dir: path to a directory where the compilation output should be generated :param import_keys: | if false, zk-snark of all modified circuits will be generated during compilation | if true, zk-snark keys for all circuits are expected to be already present in the output directory, \ and the compilation will use the provided keys to generate the verification contracts | This option is mostly used internally when connecting to a zkay contract provided by a 3rd-party :raise ZkayCompilerError: if any compilation stage fails :raise RuntimeError: if import_keys is True and zkay file, manifest file or any of the key files is missing """ # Copy zkay code to output zkay_filename = 'contract.zkay' if import_keys and not os.path.exists(os.path.join(output_dir, zkay_filename)): raise RuntimeError('Zkay file is expected to already be in the output directory when importing keys') elif not import_keys: _dump_to_output(code, output_dir, zkay_filename) # Type checking zkay_ast = get_processed_ast(code) # Contract transformation with print_step("Transforming zkay -> public contract"): ast, circuits = transform_ast(deepcopy(zkay_ast)) # Dump libraries with print_step("Write library contract files"): with cfg.library_compilation_environment(): for crypto_params in ast.used_crypto_backends: # Write pki contract pki_contract_code = library_contracts.get_pki_contract(crypto_params) pki_contract_file = f'{cfg.get_pki_contract_name(crypto_params)}.sol' _dump_to_output(pki_contract_code, output_dir, pki_contract_file, dryrun_solc=True) # Write library contract _dump_to_output(library_contracts.get_verify_libs_code(), output_dir, ProvingScheme.verify_libs_contract_filename, dryrun_solc=True) # Write public contract file with print_step('Write public solidity code'): output_filename = 'contract.sol' solidity_code_output = _dump_to_output(to_solidity(ast), output_dir, output_filename) # Get all circuit helpers for the transformed contract circuits: List[CircuitHelper] = list(circuits.values()) # Generate offchain simulation code (transforms transactions, interface to deploy and access the zkay contract) offchain_simulation_code = PythonOffchainVisitor(circuits).visit(ast) _dump_to_output(offchain_simulation_code, output_dir, 'contract.py') # Instantiate proving scheme and circuit generator ps = proving_scheme_classes[cfg.proving_scheme]() cg = generator_classes[cfg.snark_backend](circuits, ps, output_dir) if 'verifier_names' in kwargs: assert isinstance(kwargs['verifier_names'], list) verifier_names = get_verification_contract_names(zkay_ast) assert sorted(verifier_names) == sorted([cc.verifier_contract_type.code() for cc in cg.circuits_to_prove]) kwargs['verifier_names'][:] = verifier_names[:] # Generate manifest if not import_keys: with print_step("Writing manifest file"): # Set crypto backends for unused homomorphisms to None for hom in Homomorphism: if hom not in ast.used_homomorphisms: cfg.set_crypto_backend(hom, None) manifest = { Manifest.zkay_version: cfg.zkay_version, Manifest.solc_version: cfg.solc_version, Manifest.zkay_options: cfg.export_compiler_settings(), } _dump_to_output(json.dumps(manifest), output_dir, 'manifest.json') elif not os.path.exists(os.path.join(output_dir, 'manifest.json')): raise RuntimeError('Zkay contract import failed: Manifest file is missing') # Generate circuits and corresponding verification contracts cg.generate_circuits(import_keys=import_keys) # Check that all verification contracts and the main contract compile main_solidity_files = cg.get_verification_contract_filenames() + [os.path.join(output_dir, output_filename)] for f in main_solidity_files: check_compilation(f, show_errors=False) return cg, solidity_code_output