Source code for pytezos.michelson.converter

from pprint import pprint
from collections import namedtuple

from pytezos.michelson.forge import prim_tags
from pytezos.michelson.micheline import Schema, collapse_micheline, build_maps, parse_micheline, \
    parse_json, make_micheline, michelson_to_micheline
from pytezos.michelson.formatter import micheline_to_michelson
from pytezos.michelson.docstring import generate_docstring

BigMapSchema = namedtuple('BigMapSchema', ['bin_to_id', 'id_to_bin'])


[docs]class MichelineSchemaError(ValueError): pass
[docs]def build_schema(code) -> Schema: """ Creates internal structures necessary for decoding/encoding micheline: `metadata` -> micheline tree with collapsed `pair`, `or`, and `option` nodes `bin_types` -> maps binary path to primitive `bin_names` -> binary path to key name mapping `json_to_bin` -> json path to binary path mapping :param code: parameter or storage section of smart contract source code (in micheline) :rtype: Schema """ try: metadata = collapse_micheline(code) return Schema(metadata, *build_maps(metadata)) except (KeyError, ValueError, TypeError) as e: pprint(code, compact=True) raise MichelineSchemaError(f'Failed to build schema', e.args)
[docs]def decode_micheline(val_expr, type_expr, schema: Schema, root='0'): """ Converts Micheline data into Python object. :param val_expr: Micheline value expression :param type_expr: Michelson type expression for the entire type :param schema: schema built for particular contract/section :param root: which binary node to take as root, used to decode BigMap values/diffs :returns: Object """ try: return parse_micheline(val_expr, type_expr, schema, root) except (KeyError, IndexError, TypeError) as e: print(generate_docstring(schema, 'schema')) pprint(val_expr, compact=True) raise MichelineSchemaError(f'Failed to decode micheline expression', e.args)
[docs]def encode_micheline(data, schema: Schema, root='0', binary=False): """ Converts Python object into Micheline expression. :param data: Python object :param schema: schema built for particular contract/section :param root: which binary node to take as root, used to encode BigMap values :param binary: Encode keys and addresses in bytes rather than strings, default is False :returns: Micheline expression """ try: bin_values = parse_json(data, schema, root) return make_micheline(bin_values, schema.bin_types, root, binary) except (KeyError, IndexError, TypeError) as e: print(generate_docstring(schema, 'schema')) pprint(data, compact=True) raise MichelineSchemaError(f'Failed to encode micheline expression', e.args)
[docs]def convert(source, schema: Schema = None, output='micheline', inline=False): """ Convert data between different representations (DO NOT USE FOR STORAGE/PARAMETER, can be ambiguous). :param source: Data, can be one of Michelson (string), Micheline expression, object :param schema: Needed if decoding/encoding objects (optional) :param output: Output format, one of 'micheline' (default), 'michelson', 'object' :param inline: Used for michelson output, whether to omit line breaks """ if isinstance(source, str): try: source = michelson_to_micheline(source) except ValueError: assert schema source = encode_micheline(source, schema) elif not is_micheline(source): assert schema source = encode_micheline(source, schema) if output == 'michelson': return micheline_to_michelson(source, inline) elif output == 'object': assert False, f'not supported' elif output == 'micheline': return source else: assert False, output
[docs]def build_big_map_schema(data, schema: Schema) -> BigMapSchema: """ Generate Big_map schema from the contract storage :param data: Raw storage (Micheline expression) :param schema: Storage schema :returns: Mappings: Big_map id to JSON path and vice versa :rtype: BigMapSchema """ bin_to_id = dict() id_to_bin = dict() def scan_big_map_ids(node, path): if len(path) == 0: assert node.get('int'), (node, path) yield int(node['int']) elif isinstance(node, list): for item in node: yield from scan_big_map_ids(item, path) else: assert node.get('args'), (node, path) yield from scan_big_map_ids(node['args'][int(path[0])], path[1:]) for bin_path, prim in schema.bin_types.items(): if prim == 'big_map': for big_map_id in scan_big_map_ids(data, bin_path[1:]): bin_to_id[bin_path], id_to_bin[big_map_id] = big_map_id, bin_path return BigMapSchema(bin_to_id, id_to_bin)
[docs]def is_micheline(value) -> bool: """ Check if value is a Micheline expression (using heuristics, so not 100% accurate). :param value: Object :rtype: bool """ if isinstance(value, list): def get_prim(x): return x.get('prim') if isinstance(x, dict) else None return set(map(get_prim, value)) == {'parameter', 'storage', 'code'} elif isinstance(value, dict): primitives = list(prim_tags.keys()) return any(map(lambda x: x in value, ['prim', 'args', 'annots', *primitives])) else: return False