diff --git a/examples/mac/test_mac.py b/examples/mac/test_mac.py index 5a5b6946c..ea13bfc6f 100644 --- a/examples/mac/test_mac.py +++ b/examples/mac/test_mac.py @@ -2,8 +2,6 @@ from unittest import TestCase from pytezos import pytezos, ContractInterface -from pytezos.repl.interpreter import Interpreter -from pytezos.michelson.contract import micheline_to_michelson initial_storage = { 'admin': { @@ -35,36 +33,22 @@ class TestMac(TestCase): @classmethod def setUpClass(cls): - cls.i = Interpreter() cls.mac = ContractInterface.create_from(join(dirname(__file__), 'mac.tz')) cls.maxDiff = None def test_pause(self): - res = self.mac.pause(True).result( + res = self.mac.pause(True).interpret( storage=initial_storage, - source=pytezos.key.public_key_hash()) + source=pytezos.key.public_key_hash(), + sender=pytezos.key.public_key_hash()) self.assertTrue(res.storage['admin']['paused']) def test_is_operator_callback(self): - param = { - 'callback': 'KT1V4jijVy1HfVWde6HBVD1cCygZDtFJK4Xz', - 'operator': { - 'operator': pytezos.key.public_key_hash(), - 'owner': pytezos.key.public_key_hash(), - 'tokens': {'all_tokens': None} - } - } - parameter = micheline_to_michelson( - self.mac.contract.parameter.encode(param, entrypoint='is_operator')['value']) - storage = micheline_to_michelson( - self.mac.contract.storage.encode(initial_storage)) - - self.i.execute(f'INCLUDE "{join(dirname(__file__), "mac.tz")}"') - res = self.i.execute(f'RUN %is_operator ({parameter}) ({storage})') - - self.assertTrue(res['success']) - self.assertIsNotNone(res['result'].get('operations')) - self.assertEqual(1, len(res['result']['operations'])) - - content = res['result']['operations'][0].content - print(micheline_to_michelson(content['parameters']['value'])) + res = self.mac.is_operator(callback='KT1V4jijVy1HfVWde6HBVD1cCygZDtFJK4Xz', # does not matter + operator={ + 'operator': pytezos.key.public_key_hash(), + 'owner': pytezos.key.public_key_hash(), + 'tokens': {'all_tokens': None} + }) \ + .interpret(storage=initial_storage) + self.assertEqual(1, len(res.operations)) diff --git a/pytezos/michelson/interface.py b/pytezos/michelson/interface.py index 90e8633c4..afe032a80 100644 --- a/pytezos/michelson/interface.py +++ b/pytezos/michelson/interface.py @@ -11,6 +11,7 @@ from pytezos.operation.content import format_mutez, format_tez from pytezos.interop import Interop from pytezos.tools.docstring import get_class_docstring +from pytezos.repl.interpreter import Interpreter class ContractCallResult(OperationResult): @@ -50,6 +51,15 @@ def from_code_run(cls, code_run: dict, parameters, contract: Contract): operations=code_run.get('operations', []) ) + @classmethod + def from_repl_result(cls, res: dict, parameters, contract: Contract): + return cls( + parameters=contract.parameter.decode(parameters), + storage=contract.storage.decode(res['result']['storage'].val_expr), + big_map_diff=contract.storage.big_map_diff_decode(res['result']['big_map_diff']), + operations=[x.content for x in res['result']['operations']] + ) + class ContractCall(Interop): @@ -126,6 +136,41 @@ def cmdline(self): return f'transfer {amount} from {source} to {self.address} ' \ f'--entrypoint \'{entrypoint}\' --arg \'{arg}\'' + def interpret(self, storage, source=None, sender=None, amount=None, balance=None, chain_id=None, now=None): + """ + Run code in the builtin REPL (WARNING! Not recommended for critical tasks) + :param storage: Python object + :param source: patch SOURCE + :param sender: patch SENDER + :param amount: patch AMOUNT + :param balance: patch BALANCE + :param chain_id: patch CHAIN_ID + :param now: patch NOW + :return: ContractCallResult + """ + i = Interpreter() + i.execute(self.contract.text) + + patch_map = { + 'SOURCE': source, + 'SENDER': sender, + 'AMOUNT': amount, + 'BALANCE': balance, + 'CHAIN_ID': chain_id, + 'NOW': now + } + for instr, value in patch_map.items(): + if value is not None: + value = f'"{value}"' if isinstance(value, str) else value + i.execute(f'PATCH {instr} {value}') + + s_expr = micheline_to_michelson(self.contract.storage.encode(storage), inline=True) + p_expr = micheline_to_michelson(self.parameters['value'], inline=True) + res = i.execute(f'RUN %{self.parameters["entrypoint"]} ({p_expr}) ({s_expr})') + + return ContractCallResult.from_repl_result( + res, parameters=self.parameters, contract=self.contract) + def result(self, storage=None, source=None, sender=None, gas_limit=None): """ Simulate operation and parse the result. diff --git a/pytezos/repl/big_map.py b/pytezos/repl/big_map.py index 346463b05..aa6cfb551 100644 --- a/pytezos/repl/big_map.py +++ b/pytezos/repl/big_map.py @@ -1,7 +1,7 @@ from typing import Dict from simplejson import JSONDecodeError -from pytezos import pytezos +from pytezos.interop import Interop from pytezos.repl.types import StackItem, Map, BigMap from pytezos.repl.parser import parse_expression, assert_expr_equal, get_int, assert_comparable, assert_big_map_val from pytezos.michelson.pack import get_key_hash @@ -168,7 +168,7 @@ def _get_big_map_val(self, big_map: BigMap, key: StackItem): key_hash = get_key_hash(key.val_expr, key.type_expr) network = big_map.val_expr['_network'] try: - res = pytezos.using(network).shell.head.context.big_maps[int(big_map)][key_hash]() + res = Interop().using(network).shell.head.context.big_maps[int(big_map)][key_hash]() except JSONDecodeError: res = None return res diff --git a/pytezos/repl/blockchain.py b/pytezos/repl/blockchain.py index 023d0a878..113bd4917 100644 --- a/pytezos/repl/blockchain.py +++ b/pytezos/repl/blockchain.py @@ -1,6 +1,6 @@ from datetime import datetime -from pytezos import pytezos +from pytezos.interop import Interop from pytezos.repl.control import instruction from pytezos.repl.context import Context from pytezos.repl.types import assert_stack_type, Mutez, ChainID, Address, Contract, Option, assert_equal_types, \ @@ -69,14 +69,14 @@ def do_self(ctx: Context, prim, args, annots): @instruction('SENDER') def do_sender(ctx: Context, prim, args, annots): res = ctx.get('SENDER') - assert res, f'SENDER is not initialized' + assert res is not None, f'SENDER is not initialized' ctx.push(res, annots=['@sender']) @instruction('SOURCE') def do_source(ctx: Context, prim, args, annots): res = ctx.get('SOURCE') - assert res, f'SOURCE is not initialized' + assert res is not None, f'SOURCE is not initialized' ctx.push(res, annots=['@source']) @@ -86,7 +86,12 @@ def do_now(ctx: Context, prim, args, annots): if not res: network = ctx.get('NETWORK') if network: - now = pytezos.using(network).now() + interop = Interop().using(network) + constants = interop.shell.block.context.constants() # cached + ts = interop.shell.head.header()['timestamp'] + dt = datetime.strptime(ts, '%Y-%m-%dT%H:%M:%SZ') + first_delay = constants['time_between_blocks'][0] + return int((dt - datetime(1970, 1, 1)).total_seconds()) + int(first_delay) else: now = int(datetime.utcnow().timestamp()) res = Timestamp(now) @@ -99,8 +104,9 @@ def check_contract(ctx: Context, address, entry_annot, type_expr): ctx.print('skip check') return True try: - ci = pytezos.using(network).contract(address) - actual, _ = get_entry_expr(ci.contract.parameter.code, entry_annot) + script = Interop().using(network).shell.contracts[address].script() + p_type_expr = next(s for s in script['code'] if s['prim'] == 'parameter') + actual, _ = get_entry_expr(p_type_expr, entry_annot) if expr_equal(type_expr, actual): return True else: diff --git a/pytezos/repl/context.py b/pytezos/repl/context.py index b13ebf881..5f2aaed02 100644 --- a/pytezos/repl/context.py +++ b/pytezos/repl/context.py @@ -108,7 +108,7 @@ def get(self, key, default=None): def set(self, key, value): self.meta[key] = value if key in ['parameter', 'storage', 'code', 'STORAGE']: - self.print(micheline_to_michelson({"prim": key, "args": [value]})) + self.print(micheline_to_michelson({"prim": key, "args": [value]}, inline=True)) else: self.print(f'set {key}={repr(value)}') diff --git a/pytezos/repl/helpers.py b/pytezos/repl/helpers.py index 1ce985db6..369108db2 100644 --- a/pytezos/repl/helpers.py +++ b/pytezos/repl/helpers.py @@ -1,6 +1,7 @@ from os.path import isfile -from pytezos import Contract, pytezos +from pytezos import Contract +from pytezos.interop import Interop from pytezos.encoding import is_kt from pytezos.repl.control import instruction, do_interpret from pytezos.repl.context import Context @@ -150,9 +151,9 @@ def do_include(ctx: Context, prim, args, annots): network = parts[0] if len(parts) > 1 else ctx.get('NETWORK', 'mainnet') address = parts[1] if len(parts) > 1 else parts[0] assert is_kt(address), f'expected filename or KT address (with network), got {path}' - code = pytezos.using(network).contract(address).contract.code - storage = pytezos.using(network).shell.contracts[address].storage() - ctx.set('STORAGE', storage) + script = Interop().using(network).shell.contracts[address].script() + code = script['code'] + ctx.set('STORAGE', script['storage']) do_interpret(ctx, code) @@ -177,7 +178,7 @@ def do_reset(ctx: Context, prim, args, annots): network = get_string(args[0]) assert network in networks, f'expected on of {", ".join(networks)}, got {network}' ctx.set('NETWORK', network) - chain_id = ChainID(pytezos.using(network).shell.chains.main.chain_id()) + chain_id = ChainID(Interop().using(network).shell.chains.main.chain_id()) ctx.set('CHAIN_ID', chain_id) ctx.big_maps.reset() ctx.drop_all()