diff --git a/boa3/model/builtin/builtin.py b/boa3/model/builtin/builtin.py index 02bf25d7e..f17ffa8b7 100644 --- a/boa3/model/builtin/builtin.py +++ b/boa3/model/builtin/builtin.py @@ -1,12 +1,12 @@ from typing import Dict, List, Optional, Tuple, Union +from boa3.model.builtin.builtincallable import IBuiltinCallable from boa3.model.builtin.classmethod.appendmethod import AppendMethod from boa3.model.builtin.classmethod.clearmethod import ClearMethod from boa3.model.builtin.classmethod.extendmethod import ExtendMethod from boa3.model.builtin.classmethod.mapkeysmethod import MapKeysMethod from boa3.model.builtin.classmethod.mapvaluesmethod import MapValuesMethod from boa3.model.builtin.classmethod.reversemethod import ReverseMethod -from boa3.model.builtin.decorator.builtindecorator import IBuiltinDecorator from boa3.model.builtin.decorator.eventdecorator import EventDecorator from boa3.model.builtin.decorator.metadatadecorator import MetadataDecorator from boa3.model.builtin.decorator.publicdecorator import PublicDecorator @@ -15,20 +15,20 @@ from boa3.model.builtin.method.bytearraymethod import ByteArrayMethod from boa3.model.builtin.method.lenmethod import LenMethod from boa3.model.builtin.neometadatatype import MetadataTypeSingleton as NeoMetadataType +from boa3.model.callable import Callable from boa3.model.identifiedsymbol import IdentifiedSymbol -from boa3.model.method import Method from boa3.model.type.itype import IType class Builtin: @classmethod - def get_symbol(cls, symbol_id: str) -> Optional[Method]: + def get_symbol(cls, symbol_id: str) -> Optional[Callable]: for name, method in vars(cls).items(): - if isinstance(method, IBuiltinDecorator) and method.identifier == symbol_id: + if isinstance(method, IBuiltinCallable) and method.identifier == symbol_id: return method @classmethod - def get_by_self(cls, symbol_id: str, self_type: IType) -> Optional[Method]: + def get_by_self(cls, symbol_id: str, self_type: IType) -> Optional[Callable]: for name, method in vars(cls).items(): if (isinstance(method, IBuiltinMethod) and method.identifier == symbol_id diff --git a/boa3/model/builtin/builtincallable.py b/boa3/model/builtin/builtincallable.py new file mode 100644 index 000000000..c8aab976a --- /dev/null +++ b/boa3/model/builtin/builtincallable.py @@ -0,0 +1,23 @@ +from abc import ABC +from typing import Dict, List, Tuple + +from boa3.model.callable import Callable +from boa3.model.identifiedsymbol import IdentifiedSymbol +from boa3.model.type.itype import IType +from boa3.model.variable import Variable +from boa3.neo.vm.opcode.Opcode import Opcode + + +class IBuiltinCallable(Callable, IdentifiedSymbol, ABC): + def __init__(self, identifier: str, args: Dict[str, Variable] = None, return_type: IType = None): + super().__init__(args, return_type) + self._identifier = identifier + + @property + def opcode(self) -> List[Tuple[Opcode, bytes]]: + """ + Gets the opcode for the method. + + :return: the opcode and its data if exists. None otherwise. + """ + return [] diff --git a/boa3/model/builtin/decorator/builtindecorator.py b/boa3/model/builtin/decorator/builtindecorator.py index 442840010..fb621cbad 100644 --- a/boa3/model/builtin/decorator/builtindecorator.py +++ b/boa3/model/builtin/decorator/builtindecorator.py @@ -1,17 +1,16 @@ from abc import ABC, abstractmethod from typing import Dict +from boa3.model.builtin.builtincallable import IBuiltinCallable from boa3.model.expression import IExpression -from boa3.model.identifiedsymbol import IdentifiedSymbol from boa3.model.method import Method from boa3.model.type.itype import IType from boa3.model.variable import Variable -class IBuiltinDecorator(Method, IdentifiedSymbol, ABC): +class IBuiltinDecorator(IBuiltinCallable, Method, ABC): def __init__(self, identifier: str, args: Dict[str, Variable] = None, return_type: IType = None): - super().__init__(args, return_type) - self._identifier = identifier + super().__init__(identifier, args, return_type) @abstractmethod def validate_parameters(self, *params: IExpression) -> bool: diff --git a/boa3/model/builtin/interop/interopevent.py b/boa3/model/builtin/interop/interopevent.py new file mode 100644 index 000000000..cb2b7ea86 --- /dev/null +++ b/boa3/model/builtin/interop/interopevent.py @@ -0,0 +1,28 @@ +from abc import ABC +from typing import Dict, List, Tuple + +from boa3.model.builtin.method.builtinevent import IBuiltinEvent +from boa3.model.variable import Variable +from boa3.neo.vm.opcode.Opcode import Opcode + + +class InteropEvent(IBuiltinEvent, ABC): + + def __init__(self, identifier: str, sys_call: str, args: Dict[str, Variable] = None): + self._sys_call: str = sys_call + super().__init__(identifier, args) + + @property + def interop_method_hash(self) -> bytes: + return self._method_hash(self._sys_call) + + def _method_hash(self, method_name: str) -> bytes: + from boa3.constants import SIZE_OF_INT32 + from boa3.neo import cryptography + from boa3.neo.vm.type.String import String + + return cryptography.sha256(String(method_name).to_bytes())[:SIZE_OF_INT32] + + @property + def opcode(self) -> List[Tuple[Opcode, bytes]]: + return [(Opcode.SYSCALL, self.interop_method_hash)] diff --git a/boa3/model/builtin/interop/runtime/notifymethod.py b/boa3/model/builtin/interop/runtime/notifymethod.py index 5f329875b..a38869bb1 100644 --- a/boa3/model/builtin/interop/runtime/notifymethod.py +++ b/boa3/model/builtin/interop/runtime/notifymethod.py @@ -1,14 +1,14 @@ from typing import Dict -from boa3.model.builtin.interop.interopmethod import InteropMethod +from boa3.model.builtin.interop.interopevent import InteropEvent from boa3.model.variable import Variable -class NotifyMethod(InteropMethod): +class NotifyMethod(InteropEvent): def __init__(self): from boa3.model.type.type import Type identifier = 'notify' syscall = 'System.Runtime.Notify' args: Dict[str, Variable] = {'state': Variable(Type.any)} - super().__init__(identifier, syscall, args, Type.none) + super().__init__(identifier, syscall, args) diff --git a/boa3/model/builtin/method/builtinevent.py b/boa3/model/builtin/method/builtinevent.py new file mode 100644 index 000000000..69c224d92 --- /dev/null +++ b/boa3/model/builtin/method/builtinevent.py @@ -0,0 +1,12 @@ +from abc import ABC +from typing import Dict + +from boa3.model.builtin.builtincallable import IBuiltinCallable +from boa3.model.event import Event +from boa3.model.variable import Variable + + +class IBuiltinEvent(IBuiltinCallable, Event, ABC): + def __init__(self, identifier: str, args: Dict[str, Variable] = None): + from boa3.model.type.type import Type + super().__init__(identifier, args, Type.none) diff --git a/boa3/model/builtin/method/builtinmethod.py b/boa3/model/builtin/method/builtinmethod.py index 9b3487918..7f47382d5 100644 --- a/boa3/model/builtin/method/builtinmethod.py +++ b/boa3/model/builtin/method/builtinmethod.py @@ -1,25 +1,16 @@ from abc import ABC, abstractmethod -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, Optional -from boa3.model.builtin.decorator.builtindecorator import IBuiltinDecorator +from boa3.model.builtin.builtincallable import IBuiltinCallable +from boa3.model.method import Method from boa3.model.type.itype import IType from boa3.model.variable import Variable -from boa3.neo.vm.opcode.Opcode import Opcode -class IBuiltinMethod(IBuiltinDecorator, ABC): +class IBuiltinMethod(IBuiltinCallable, Method, ABC): def __init__(self, identifier: str, args: Dict[str, Variable] = None, return_type: IType = None): super().__init__(identifier, args, return_type) - @property - def opcode(self) -> List[Tuple[Opcode, bytes]]: - """ - Gets the opcode for the method. - - :return: the opcode and its data if exists. None otherwise. - """ - return [] - @property def is_supported(self) -> bool: """ diff --git a/boa3_test/tests/test_file_generation.py b/boa3_test/tests/test_file_generation.py index c93260f2a..395e807e5 100644 --- a/boa3_test/tests/test_file_generation.py +++ b/boa3_test/tests/test_file_generation.py @@ -258,6 +258,26 @@ def test_generate_nefdbgnfo_file_with_event(self): self.assertIn(param_id, actual_event.args) self.assertEqual(param_type, actual_event.args[param_id].type.abi_type) + def test_generate_manifest_file_with_notify_event(self): + path = '%s/boa3_test/example/interop_test/NotifySequence.py' % self.dirname + expected_manifest_output = path.replace('.py', '.manifest.json') + output, manifest = self.compile_and_save(path) + + self.assertTrue(os.path.exists(expected_manifest_output)) + self.assertIn('abi', manifest) + abi = manifest['abi'] + + self.assertIn('events', abi) + self.assertGreater(len(abi['events']), 0) + + notify_event = next(abi_event for abi_event in abi['events'] + if 'name' in abi_event and abi_event['name'] == 'notify') + self.assertIsNotNone(notify_event, "notify event is not listed in the contract's abi") + self.assertIn('parameters', notify_event) + self.assertEqual(1, len(notify_event['parameters'])) + self.assertIn('type', notify_event['parameters'][0]) + self.assertEqual(AbiType.Any, notify_event['parameters'][0]['type']) + def test_generate_without_main(self): path = '%s/boa3_test/example/generation_test/GenerationWithoutMain.py' % self.dirname expected_manifest_output = path.replace('.py', '.manifest.json') diff --git a/boa3_test/tests/test_interop.py b/boa3_test/tests/test_interop.py index 9b1b85e6c..7da2e58a6 100644 --- a/boa3_test/tests/test_interop.py +++ b/boa3_test/tests/test_interop.py @@ -46,11 +46,17 @@ def test_check_witness_mismatched_type(self): self.assertCompilerLogs(MismatchedTypes, path) def test_notify_str(self): + event_name = String('notify').to_bytes() string = String('str').to_bytes() expected_output = ( Opcode.PUSHDATA1 + Integer(len(string)).to_byte_array(min_length=1) + string + + Opcode.PUSH1 + + Opcode.PACK + + Opcode.PUSHDATA1 + + Integer(len(event_name)).to_byte_array(min_length=1) + + event_name + Opcode.SYSCALL + Interop.Notify.interop_method_hash + Opcode.PUSHNULL @@ -62,8 +68,14 @@ def test_notify_str(self): self.assertEqual(expected_output, output) def test_notify_int(self): + event_name = String('notify').to_bytes() expected_output = ( Opcode.PUSH15 + + Opcode.PUSH1 + + Opcode.PACK + + Opcode.PUSHDATA1 + + Integer(len(event_name)).to_byte_array(min_length=1) + + event_name + Opcode.SYSCALL + Interop.Notify.interop_method_hash + Opcode.PUSHNULL @@ -75,8 +87,14 @@ def test_notify_int(self): self.assertEqual(expected_output, output) def test_notify_bool(self): + event_name = String('notify').to_bytes() expected_output = ( Opcode.PUSH1 + + Opcode.PUSH1 + + Opcode.PACK + + Opcode.PUSHDATA1 + + Integer(len(event_name)).to_byte_array(min_length=1) + + event_name + Opcode.SYSCALL + Interop.Notify.interop_method_hash + Opcode.PUSHNULL @@ -88,8 +106,14 @@ def test_notify_bool(self): self.assertEqual(expected_output, output) def test_notify_none(self): + event_name = String('notify').to_bytes() expected_output = ( Opcode.PUSHNULL + + Opcode.PUSH1 + + Opcode.PACK + + Opcode.PUSHDATA1 + + Integer(len(event_name)).to_byte_array(min_length=1) + + event_name + Opcode.SYSCALL + Interop.Notify.interop_method_hash + Opcode.PUSHNULL @@ -101,6 +125,7 @@ def test_notify_none(self): self.assertEqual(expected_output, output) def test_notify_sequence(self): + event_name = String('notify').to_bytes() expected_output = ( Opcode.PUSH7 + Opcode.PUSH5 @@ -108,6 +133,11 @@ def test_notify_sequence(self): + Opcode.PUSH2 + Opcode.PUSH4 + Opcode.PACK + + Opcode.PUSH1 + + Opcode.PACK + + Opcode.PUSHDATA1 + + Integer(len(event_name)).to_byte_array(min_length=1) + + event_name + Opcode.SYSCALL + Interop.Notify.interop_method_hash + Opcode.PUSHNULL