Skip to content

Commit

Permalink
expose interpreter for testing purposes
Browse files Browse the repository at this point in the history
  • Loading branch information
m-kus committed Mar 19, 2020
1 parent 0782503 commit e1ee36d
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 41 deletions.
38 changes: 11 additions & 27 deletions examples/mac/test_mac.py
Original file line number Diff line number Diff line change
Expand Up @@ -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': {
Expand Down Expand Up @@ -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))
45 changes: 45 additions & 0 deletions pytezos/michelson/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):

Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions pytezos/repl/big_map.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
18 changes: 12 additions & 6 deletions pytezos/repl/blockchain.py
Original file line number Diff line number Diff line change
@@ -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, \
Expand Down Expand Up @@ -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'])


Expand All @@ -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)
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pytezos/repl/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}')

Expand Down
11 changes: 6 additions & 5 deletions pytezos/repl/helpers.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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()

0 comments on commit e1ee36d

Please sign in to comment.