diff --git a/README.md b/README.md index 11b8208..9fdc477 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ The compiler supports a subset of the Python language ( in the same way that a _ #### Get Help or give help - Open a new [issue](https://github.com/CityOfZion/neo-boa/issues/new) if you encounter a problem. -- Or ping **@localhuman** on the [NEO Slack](https://join.slack.com/t/neoblockchainteam/shared_invite/MjE3ODMxNDUzMDE1LTE1MDA4OTY3NDQtNTMwM2MyMTc2NA). +- Or ping **@localhuman** on the [NEO official community chatroom](https://discord.gg/R8v48YA). - Pull requests welcome. New features, writing tests and documentation are all needed. @@ -124,7 +124,7 @@ Tests are important. ## License -- Open-source [MIT](https://github.com/CityOfZion/neo-python/blob/master/LICENSE.md). +- Open-source [MIT](LICENSE.md). - Main author is [@localhuman](https://github.com/localhuman). diff --git a/README.rst b/README.rst index 5cbc427..fa1c87a 100644 --- a/README.rst +++ b/README.rst @@ -3,24 +3,24 @@ Python compiler for the Neo Virtual Machine =========================================== - -- `Overview`_ -- `Installation`_ -- `Usage`_ -- `License`_ -- `Tests`_ -- `Donatitons`_ - Overview -------- -A Python compiler for the Neo Virtual Machine +The ``neo-boa`` compiler is a tool for compiling Python files to the +``.avm`` format for usage in the `Neo Virtual +Machine `__ which is used to +execute contracts on the `Neo +Blockchain `__. + +The compiler supports a subset of the Python language ( in the same way +that a *boa constrictor* is a subset of the Python snake species) What does it currently do ^^^^^^^^^^^^^^^^^^^^^^^^^ - Compiles a subset of the Python language to the ``.avm`` format for - use in the `Neo Virtual Machine`_ + use in the `Neo Virtual + Machine `__ - Works for Python 3.4 and 3.5 What will it do @@ -32,8 +32,11 @@ What will it do Get Help or give help ^^^^^^^^^^^^^^^^^^^^^ -- Open a new `issue`_ if you encounter a problem. -- Or ping **@localhuman** on the `NEO Slack`_. +- Open a new + `issue `__ if you + encounter a problem. +- Or ping **@localhuman** on the `NEO official community + chatroom `__. - Pull requests welcome. New features, writing tests and documentation are all needed. @@ -47,25 +50,48 @@ Pip pip install neo-boa +Docker +^^^^^^ + +This project contains a Dockerfile to batch compile Python smart +contracts. Clone the repository and navigate into the docker sub +directory of the project. Run the following command to build the +container: + +:: + + docker build -t neo-boa . + +The neo-boa Docker container takes a directory on the host containing +python smart contracts as an input and a directory to compile the .avm +files to as an output. It can be executed like this: + +:: + + docker run -it -v /absolute/path/input_dir:/python-contracts -v /absolute/path/output_dir:/compiled-contracts neo-boa + +The -v (volume) command maps the directories on the host to the +directories within the container. + Manual ^^^^^^ Clone the repository and navigate into the project directory. Make a -Python 3 virtual environment and activate it via +Python 3 virtual environment and activate it via: :: python3 -m venv venv source venv/bin/activate -or to install Python 3.5 specifically +or, to install Python 3.5 specifically: :: virtualenv -p /usr/local/bin/python3.5 venv source venv/bin/activate -Then install requirements +Then, install the requirements: :: @@ -74,7 +100,7 @@ Then install requirements Usage ----- -The compiler may be used like the following +The compiler may be used like in the following example: :: @@ -82,6 +108,12 @@ The compiler may be used like the following Compiler.load_and_save('path/to/your/file.py') +Docs +---- + +You can `read the docs +here `__. + Tests ----- @@ -90,21 +122,10 @@ Tests are important. License ------- -- Open-source `MIT`_. -- Main author is **localhuman** [ https://github.com/localhuman ]. +- Open-source `MIT `__. +- Main author is `localhuman `__. Donations --------- Accepted at **ATEMNPSjRVvsXmaJW4ZYJBSVuJ6uR2mjQU** - -.. _Overview: #overview -.. _Installation: #installation -.. _Usage: #usage -.. _License: #license -.. _Tests: #tests -.. _Donatitons: #donations -.. _Neo Virtual Machine: https://github.com/neo-project/neo-vm -.. _issue: https://github.com/CityOfZion/neo-boa/issues/new -.. _NEO Slack: https://join.slack.com/t/neoblockchainteam/shared_invite/MjE3ODMxNDUzMDE1LTE1MDA4OTY3NDQtNTMwM2MyMTc2NA -.. _MIT: https://github.com/CityOfZion/neo-python/blob/master/LICENSE.md \ No newline at end of file diff --git a/boa/blockchain/vm/Neo/Account.py b/boa/blockchain/vm/Neo/Account.py index c9a5a2f..0cfdd43 100644 --- a/boa/blockchain/vm/Neo/Account.py +++ b/boa/blockchain/vm/Neo/Account.py @@ -1,5 +1,5 @@ -class Account(): +class Account: @property def ScriptHash(self): diff --git a/boa/blockchain/vm/Neo/Asset.py b/boa/blockchain/vm/Neo/Asset.py index 5d24d82..e6f7eb0 100644 --- a/boa/blockchain/vm/Neo/Asset.py +++ b/boa/blockchain/vm/Neo/Asset.py @@ -1,5 +1,5 @@ -class Asset(): +class Asset: @property def AssetId(self): diff --git a/boa/blockchain/vm/Neo/Block.py b/boa/blockchain/vm/Neo/Block.py index f1b515b..48bdd88 100644 --- a/boa/blockchain/vm/Neo/Block.py +++ b/boa/blockchain/vm/Neo/Block.py @@ -1,8 +1,7 @@ -from boa.blockchain.vm.Neo.Transaction import * from boa.blockchain.vm.Neo.Header import GetIndex, GetHash, GetPrevHash, GetTimestamp, GetVersion, GetNextConsensus, GetMerkleRoot, GetConsensusData -class Block(): +class Block: @property def TransactionCount(self): @@ -22,6 +21,11 @@ def Transactions(self): @property def Index(self): + """ + + Returns: + + """ return GetIndex(self) @property @@ -81,7 +85,7 @@ def NextConsensus(self): return GetNextConsensus(self) -def GetTransactionCount(block: Block) -> int: +def GetTransactionCount(block): """ returns the number of transactions in a block @@ -91,7 +95,7 @@ def GetTransactionCount(block: Block) -> int: pass -def GetTransactions(block: Block) -> list: +def GetTransactions(block): """ returns a list of transactions contained in a block @@ -100,7 +104,7 @@ def GetTransactions(block: Block) -> list: pass -def GetTransaction(block: Block, index: int) -> Transaction: +def GetTransaction(block, index): """ :param block: the block to get the transaction from diff --git a/boa/blockchain/vm/Neo/Blockchain.py b/boa/blockchain/vm/Neo/Blockchain.py index da8f71b..5d11213 100644 --- a/boa/blockchain/vm/Neo/Blockchain.py +++ b/boa/blockchain/vm/Neo/Blockchain.py @@ -1,19 +1,13 @@ -from boa.blockchain.vm.Neo.Header import * -from boa.blockchain.vm.Neo.Block import * -from boa.blockchain.vm.Neo.Transaction import * -from boa.blockchain.vm.Neo.Account import * -from boa.blockchain.vm.Neo.Asset import * -from boa.blockchain.vm.Neo.Contract import * -def GetHeight() -> int: +def GetHeight(): """ """ pass -def GetHeader(height_or_hash) -> Header: +def GetHeader(height_or_hash): """ :param height_or_hash: @@ -21,7 +15,7 @@ def GetHeader(height_or_hash) -> Header: pass -def GetBlock(height_or_hash) -> Block: +def GetBlock(height_or_hash): """ :param height_or_hash: @@ -29,7 +23,7 @@ def GetBlock(height_or_hash) -> Block: pass -def GetTransaction(hash) -> Transaction: +def GetTransaction(hash): """ :param hash: @@ -37,7 +31,7 @@ def GetTransaction(hash) -> Transaction: pass -def GetAccount(script_hash) -> Account: +def GetAccount(script_hash): """ :param script_hash: @@ -45,14 +39,14 @@ def GetAccount(script_hash) -> Account: pass -def GetValidators() -> []: +def GetValidators(): """ """ pass -def GetAsset(asset_id) -> Asset: +def GetAsset(asset_id): """ :param asset_id: @@ -60,7 +54,7 @@ def GetAsset(asset_id) -> Asset: pass -def GetContract(script_hash) -> Contract: +def GetContract(script_hash): """ :param script_hash: diff --git a/boa/blockchain/vm/Neo/Contract.py b/boa/blockchain/vm/Neo/Contract.py index 9106f6c..c502e3b 100644 --- a/boa/blockchain/vm/Neo/Contract.py +++ b/boa/blockchain/vm/Neo/Contract.py @@ -1,5 +1,5 @@ -class Contract(): +class Contract: @property def Script(self): diff --git a/boa/blockchain/vm/Neo/Header.py b/boa/blockchain/vm/Neo/Header.py index 66f5a3c..b7492d8 100644 --- a/boa/blockchain/vm/Neo/Header.py +++ b/boa/blockchain/vm/Neo/Header.py @@ -1,6 +1,6 @@ -class Header(): +class Header: def getmyhash(self): return GetHash(self) @@ -66,7 +66,7 @@ def NextConsensus(self): return GetNextConsensus(self) -def GetIndex(header: Header) -> int: +def GetIndex(header): """ Returns the height/index of a header Args: @@ -78,7 +78,7 @@ def GetIndex(header: Header) -> int: pass -def GetHash(header: Header) -> bytearray: +def GetHash(header): """ gets the hash of the header @@ -87,7 +87,7 @@ def GetHash(header: Header) -> bytearray: pass -def GetVersion(header: Header) -> int: +def GetVersion(header): """ gets the version of the header @@ -96,7 +96,7 @@ def GetVersion(header: Header) -> int: pass -def GetPrevHash(header: Header) -> bytearray: +def GetPrevHash(header): """ gets the hash of the previous header in the blockchain @@ -105,7 +105,7 @@ def GetPrevHash(header: Header) -> bytearray: pass -def GetMerkleRoot(header: Header) -> bytearray: +def GetMerkleRoot(header): """ gets the merkle root of the transactions contained in the block @@ -114,7 +114,7 @@ def GetMerkleRoot(header: Header) -> bytearray: pass -def GetTimestamp(header: Header) -> int: +def GetTimestamp(header): """ gets the timestamp of when the header was created @@ -123,7 +123,7 @@ def GetTimestamp(header: Header) -> int: pass -def GetConsensusData(header: Header) -> bytearray: +def GetConsensusData(header): """ gets the address of the consensus @@ -132,7 +132,7 @@ def GetConsensusData(header: Header) -> bytearray: pass -def GetNextConsensus(header: Header) -> bytearray: +def GetNextConsensus(header): """ gets the address where the next consensus will occur diff --git a/boa/blockchain/vm/Neo/Input.py b/boa/blockchain/vm/Neo/Input.py index 904f238..bfa2347 100644 --- a/boa/blockchain/vm/Neo/Input.py +++ b/boa/blockchain/vm/Neo/Input.py @@ -1,5 +1,5 @@ -class TransactionInput(): +class TransactionInput: @property def Hash(self): diff --git a/boa/blockchain/vm/Neo/Output.py b/boa/blockchain/vm/Neo/Output.py index 1c8032a..b804acf 100644 --- a/boa/blockchain/vm/Neo/Output.py +++ b/boa/blockchain/vm/Neo/Output.py @@ -1,5 +1,5 @@ -class TransactionOutput(): +class TransactionOutput: @property def AssetId(self): diff --git a/boa/blockchain/vm/Neo/Transaction.py b/boa/blockchain/vm/Neo/Transaction.py index 52187d9..a12e9f6 100644 --- a/boa/blockchain/vm/Neo/Transaction.py +++ b/boa/blockchain/vm/Neo/Transaction.py @@ -1,5 +1,5 @@ -class Transaction(): +class Transaction: @property def Hash(self): diff --git a/boa/blockchain/vm/Neo/TransactionAttribute.py b/boa/blockchain/vm/Neo/TransactionAttribute.py index 9b735ea..f534d30 100644 --- a/boa/blockchain/vm/Neo/TransactionAttribute.py +++ b/boa/blockchain/vm/Neo/TransactionAttribute.py @@ -1,5 +1,5 @@ -class TransactionAttribute(): +class TransactionAttribute: @property def Usage(self): diff --git a/boa/blockchain/vm/Neo/Validator.py b/boa/blockchain/vm/Neo/Validator.py index 678a211..4313c79 100644 --- a/boa/blockchain/vm/Neo/Validator.py +++ b/boa/blockchain/vm/Neo/Validator.py @@ -1,5 +1,5 @@ -class Validator(): +class Validator: pass diff --git a/boa/blockchain/vm/SmartContract.py b/boa/blockchain/vm/SmartContract.py index 594c34e..7e1e9b1 100644 --- a/boa/blockchain/vm/SmartContract.py +++ b/boa/blockchain/vm/SmartContract.py @@ -1,5 +1,5 @@ -class SmartContract(): +class SmartContract: def Sha1(data): """ diff --git a/boa/blockchain/vm/System/ExecutionEngine.py b/boa/blockchain/vm/System/ExecutionEngine.py index 57135ee..705317f 100644 --- a/boa/blockchain/vm/System/ExecutionEngine.py +++ b/boa/blockchain/vm/System/ExecutionEngine.py @@ -1,5 +1,5 @@ -class ExecutionEngine(): +class ExecutionEngine: """ Not used. diff --git a/boa/blockchain/vm/VMOp.py b/boa/blockchain/vm/VMOp.py index 9286ee5..b0233b1 100644 --- a/boa/blockchain/vm/VMOp.py +++ b/boa/blockchain/vm/VMOp.py @@ -128,6 +128,8 @@ NEWSTRUCT = b'\xC6' # 用作值類型 APPEND = b'\xC8' REVERSE = b'\xC9' +REMOVE = b'\xCA' + DEBUGOP = b'\xFB' @@ -138,7 +140,7 @@ items = dir(sys.modules[__name__]) -def ToName(op): +def to_name(op): """ :param op: @@ -151,18 +153,10 @@ def ToName(op): n = getattr(module, item) try: - nn = int(binascii.hexlify(n)) - + nn = int.from_bytes(n, 'little') if op == nn: return item - except Exception as e: - pass - - try: - nn2 = int.from_bytes(n, 'little') - if op == nn2: - return item - except Exception as e: + except Exception: pass return None diff --git a/boa/code/block.py b/boa/code/block.py index f8e3b48..185d1c3 100644 --- a/boa/code/block.py +++ b/boa/code/block.py @@ -1,12 +1,12 @@ -from byteplay3 import Opcode, Label +from byteplay3 import Opcode from boa.code.pytoken import PyToken from boa.code import pyop from boa.code.vmtoken import NEO_SC_FRAMEWORK import pdb -class Block(): +class Block(object): """ @@ -82,7 +82,7 @@ def has_load_attr(self): """ for token in self.oplist: if token.py_op == pyop.LOAD_ATTR and token.instance_type is None: - if token.args not in ['reverse', 'append', ]: + if token.args not in ['reverse', 'append', 'remove', ]: return True return False @@ -529,13 +529,12 @@ def process_iter_body(self, setup_block): def lookup_return_types(self, orig_method): ivars = {} klass_type = None - return_type = None for index, token in enumerate(self.oplist): if token.py_op == pyop.CALL_FUNCTION: param_count = token.args # why would param count be 256 when calling w/ kwargs? - # when keyword args are sent, the param count is 256 * num paramms? + # when keyword args are sent, the param count is 256 * num params? if param_count % 256 == 0: param_count = 2 * int(param_count / 256) diff --git a/boa/code/items.py b/boa/code/items.py index 1db744e..99ba769 100644 --- a/boa/code/items.py +++ b/boa/code/items.py @@ -1,27 +1,19 @@ -from byteplay3 import Code, Opcode -from boa.code.method import Method +from byteplay3 import Opcode from boa.code.pytoken import PyToken -from boa.code import pyop import importlib import binascii import sys import os -from byteplay3 import Code, SetLinenoType, Label +from byteplay3 import Code, SetLinenoType from boa.code import pyop from boa.code.line import Line from boa.code.method import Method -from boa.blockchain.vm import VMOp - -from collections import OrderedDict - -import pdb - -class Item(): +class Item(object): """ """ @@ -163,10 +155,10 @@ def script_hash_addr(self): :return: """ - return SmartContractAppCall.ToScriptHashData(self.script_hash) + return SmartContractAppCall.to_script_hash_data(self.script_hash) @staticmethod - def ToScriptHashData(item): + def to_script_hash_data(item): """ :return: diff --git a/boa/code/line.py b/boa/code/line.py index 53c8cc9..459e20d 100644 --- a/boa/code/line.py +++ b/boa/code/line.py @@ -3,7 +3,7 @@ from boa.code import pyop -class Line(): +class Line(object): """ @@ -30,9 +30,9 @@ def is_constant(self): :return: """ - - return (len(self.items) == 3 or len(self.items) == 5) and self.items[1][0] == pyop.LOAD_CONST and self.items[2][0] == pyop.STORE_NAME -# return False + is_correct_length = len(self.items) == 3 or len(self.items) == 5 + is_storing_constant = self.items[1][0] == pyop.LOAD_CONST and self.items[2][0] == pyop.STORE_NAME + return is_correct_length and is_storing_constant @property def is_module_method_call(self): diff --git a/boa/code/method.py b/boa/code/method.py index acd1e84..ae18ea8 100644 --- a/boa/code/method.py +++ b/boa/code/method.py @@ -10,12 +10,9 @@ import dis import collections -import pdb -from byteplay3 import object_attributes, print_attr_values, printcodelist - -class Method(): +class Method(object): """ The method is the main unit of functionality. Any method can take 0 to many arguments and return 1 value @@ -32,8 +29,9 @@ class Method(): The VMTokenizer is responsible for turning PyToken objects into VMToken objects. - When the method has been tokenized, each token then has an address within the method. Once these addresses are complete, - the ``convert_jumps`` method is called to tell each flow control operation where (which address) it will need to jump to. + When the method has been tokenized, each token then has an address within the method. Once these addresses are + complete, the ``convert_jumps`` method is called to tell each flow control operation where (which address) it will + need to jump. """ bp = None @@ -356,7 +354,6 @@ def read_initial_tokens(self): current_label = op else: - instance_type = None if op in [pyop.STORE_FAST, pyop.STORE_NAME, pyop.STORE_GLOBAL] and arg not in self.local_stores.keys(): self._check_for_type(arg, total_lines) diff --git a/boa/code/module.py b/boa/code/module.py index cbfb736..84adc53 100644 --- a/boa/code/module.py +++ b/boa/code/module.py @@ -9,10 +9,8 @@ from collections import OrderedDict -import pdb - -class Module(): +class Module(object): """ A Module is the top level component which contains code objects. When, for example, compiling ``path/to/my/file.py``, the items contained in ``file.py`` are the module. @@ -491,6 +489,9 @@ def to_s(self): 61 242 [data] 62 RETURN_VALUE [data] """ + # Initialize if needed + if self.all_vm_tokens is None: + self.link_methods() lineno = 0 pstart = True @@ -534,13 +535,27 @@ def to_s(self): pass if pt.py_op == pyop.CALL_FUNCTION: - old = to_label - to_label = '%s %s %s' % (pt.func_name, pt.func_params, old) + if to_label is None: + old = "" + else: + old = to_label + param_string = "(" + for param in pt.func_params: + param_string += str(param.args) + ", " + param_string = param_string.rstrip(", ") + ")" + to_label = '%s %s %s' % (pt.func_name, param_string, old) lno = "{:<10}".format( pt.line_no if do_print_line_no or pstart else '') addr = "{:<5}".format(key) op = "{:<20}".format(str(pt.py_op)) + + # If this is a number, it is likely a custom python opcode, get the name + if str(pt.py_op).isnumeric(): + opname = pyop.to_name(int(str(pt.py_op))) + if opname is not None: + op = "{:<20}".format(opname) + arg = "{:<50}".format( to_label if to_label is not None else pt.arg_s) data = "[data] {:<20}".format(ds) diff --git a/boa/code/pyop.py b/boa/code/pyop.py index c45c36b..53dcf35 100644 --- a/boa/code/pyop.py +++ b/boa/code/pyop.py @@ -1,4 +1,5 @@ - +import importlib +import sys # the following are python opcodes taken from the `opcode` module # these have been constantized for easier access # these are the opcodes used by python @@ -180,3 +181,22 @@ LOAD_CLASS_ATTR = 249 DEBUG_OP = 250 + +# the following is a convienience method +# for a human readable version of the ops + +module = importlib.import_module('boa.code.pyop') +items = dir(sys.modules[__name__]) + + +def to_name(op): + """ + + :param op: + :return: + """ + for item in items: + n = getattr(module, item) + if op == n: + return item + return None diff --git a/boa/code/pytoken.py b/boa/code/pytoken.py index ea96076..f813700 100644 --- a/boa/code/pytoken.py +++ b/boa/code/pytoken.py @@ -1,21 +1,18 @@ from boa.code import pyop -from byteplay3 import Label, isopcode, haslocal, Code +from byteplay3 import Label, isopcode, haslocal from opcode import opname from boa.blockchain.vm import VMOp -import pdb -import inspect - NON_RETURN_SYS_CALLS = ['Notify', 'print', 'Log', 'Put', 'Register', - 'reverse', 'append', + 'reverse', 'append', 'remove', 'Delete', 'SetVotes', 'ContractDestroy', 'MerkleRoot', 'Hash', 'PrevHash', 'GetHeader', ] -class PyToken(): +class PyToken(object): """ @@ -111,8 +108,6 @@ def __init__(self, op, lineno, index=None, args=None, array_item=None): self.array_item = array_item def __str__(self): - arg = '' - if self.args: if type(self.args) is Label: arg = str(self.args) @@ -225,7 +220,8 @@ def to_vm(self, tokenizer, prev_token=None): elif op in [pyop.BINARY_MULTIPLY, pyop.INPLACE_MULTIPLY]: token = tokenizer.convert1(VMOp.MUL, self) - elif op in [pyop.BINARY_FLOOR_DIVIDE, pyop.BINARY_TRUE_DIVIDE, pyop.INPLACE_FLOOR_DIVIDE, pyop.INPLACE_TRUE_DIVIDE]: + elif op in [pyop.BINARY_FLOOR_DIVIDE, pyop.BINARY_TRUE_DIVIDE, + pyop.INPLACE_FLOOR_DIVIDE, pyop.INPLACE_TRUE_DIVIDE]: token = tokenizer.convert1(VMOp.DIV, self) elif op in [pyop.BINARY_MODULO, pyop.INPLACE_MODULO]: @@ -267,7 +263,7 @@ def to_vm(self, tokenizer, prev_token=None): # arrays elif op == pyop.BUILD_LIST: - token = tokenizer.convert_new_array(VMOp.NEWARRAY, self) + token = tokenizer.convert_new_array(self) elif op == pyop.SETITEM: token = tokenizer.convert_set_element(self, self.args) # token = tokenizer.convert1(VMOp.SETITEM,self, data=self.args) diff --git a/boa/code/vmtoken.py b/boa/code/vmtoken.py index 815e89e..887850d 100644 --- a/boa/code/vmtoken.py +++ b/boa/code/vmtoken.py @@ -1,18 +1,14 @@ +import pdb +from collections import OrderedDict from boa.code import pyop - from byteplay3 import Label - from boa.blockchain.vm import VMOp from boa.blockchain.vm.BigInteger import BigInteger -from collections import OrderedDict - NEO_SC_FRAMEWORK = 'boa.blockchain.vm.' -import pdb - -class VMToken(): +class VMToken(object): """ """ @@ -61,7 +57,7 @@ def __init__(self, vm_op=None, pytoken=None, addr=None, data=None): self.is_annotation = False -class VMTokenizer(): +class VMTokenizer(object): """ """ @@ -122,7 +118,7 @@ def to_s(self): if type(ds) is not int and len(ds) < 1: try: ds = value.data.decode('utf-8') - except Exception as e: + except Exception: pass if pt.py_op == pyop.CALL_FUNCTION: @@ -255,7 +251,7 @@ def update_push_integer(self, vmtoken, i): return self.update1(vmtoken, VMOp.PUSH0) elif i == -1: return self.insert1(vmtoken, VMOp.PUSHM1) - elif i > 0 and i <= 16: + elif 0 < i <= 16: out = 0x50 + i return self.update1(vmtoken, out) @@ -324,7 +320,7 @@ def insert_push_integer(self, i): return self.insert1(VMOp.PUSH0) elif i == -1: return self.insert1(VMOp.PUSHM1) - elif i > 0 and i <= 16: + elif 0 < i <= 16: out = 0x50 + i return self.insert1(out) @@ -355,14 +351,12 @@ def convert1(self, vm_op, py_token=None, data=None): return vmtoken - def convert_new_array(self, vm_op, py_token=None, data=None): + def convert_new_array(self, py_token=None): # push the length of the array """ - :param vm_op: :param py_token: - :param data: """ if type(py_token.args) is int: @@ -379,7 +373,6 @@ def convert_pop_jmp_if(self, pytoken): return token def convert_load_const(self, pytoken): - token = None if type(pytoken.args) is int: token = self.convert_push_integer(pytoken.args, pytoken) elif type(pytoken.args) is str: @@ -392,12 +385,11 @@ def convert_load_const(self, pytoken): token = self.convert_push_data(bytes(pytoken.args), pytoken) elif type(pytoken.args) is bool: token = self.convert_push_integer(pytoken.args) - elif type(pytoken.args) == type(None): + elif isinstance(pytoken.args, type(None)): token = self.convert_push_data(bytearray(0)) # elif type(pytoken.args) == Code: # pass else: - raise Exception("Could not load type %s for item %s " % ( type(pytoken.args), pytoken.args)) return token @@ -440,7 +432,7 @@ def convert_push_integer(self, i, py_token=None): return self.convert1(VMOp.PUSH0, py_token=py_token) elif i == -1: return self.convert1(VMOp.PUSHM1, py_token=py_token) - elif i > 0 and i <= 16: + elif 0 < i <= 16: out = 0x50 + i return self.convert1(out, py_token=py_token) @@ -542,7 +534,7 @@ def insert_unknown_type(self, item): elif type(item) is bool: self.insert_push_data(item) - elif type(item) == type(None): + elif isinstance(item, type(None)): self.insert_push_data(bytearray(0)) else: raise Exception("Could not load type %s for item %s " % @@ -598,7 +590,6 @@ def convert_built_in_list(self, pytoken): :param pytoken: """ - new_array_len = 0 lenfound = False for index, token in enumerate(pytoken.func_params): @@ -740,14 +731,15 @@ def convert_method_call(self, pytoken): return vmtoken - def is_op_call(self, op): + @staticmethod + def is_op_call(op): """ :param op: :return: """ if op in ['len', 'abs', 'min', 'max', 'concat', 'take', 'substr', - 'reverse', 'append', + 'reverse', 'append', 'remove', 'sha1', 'sha256', 'hash160', 'hash256', 'verify_signature', 'verify_signatures']: return True @@ -789,12 +781,13 @@ def convert_op_call(self, op, pytoken=None): elif op == 'reverse': return self.convert1(VMOp.REVERSE, pytoken) elif op == 'append': - # pdb.set_trace() return self.convert1(VMOp.APPEND, pytoken) - + elif op == 'remove': + return self.convert1(VMOp.REMOVE, pytoken) return None - def is_sys_call(self, op): + @staticmethod + def is_sys_call(op): """ :param op: @@ -820,7 +813,8 @@ def convert_sys_call(self, op, pytoken=None): self.insert1(VMOp.NOP) return vmtoken - def is_built_in(self, op): + @staticmethod + def is_built_in(op): """ :param op: diff --git a/boa/compiler.py b/boa/compiler.py index 1bf7f23..dc4581f 100644 --- a/boa/compiler.py +++ b/boa/compiler.py @@ -2,7 +2,7 @@ from boa.code.module import Module -class Compiler(): +class Compiler(object): """ The main compiler interface class. @@ -54,7 +54,7 @@ def default(self): try: return self.modules[0] - except Exception as e: + except Exception: pass @staticmethod diff --git a/boa/tests/src/AppendTest.py b/boa/tests/src/AppendTest.py index 746092b..50c0d20 100644 --- a/boa/tests/src/AppendTest.py +++ b/boa/tests/src/AppendTest.py @@ -6,7 +6,7 @@ def Main(): :return: """ - m = [1, 2, 4, 'blah'] + m = [1, 2, 2] m.append(7) diff --git a/boa/tests/src/ArrayRemoveTest.py b/boa/tests/src/ArrayRemoveTest.py new file mode 100644 index 0000000..0db7b70 --- /dev/null +++ b/boa/tests/src/ArrayRemoveTest.py @@ -0,0 +1,13 @@ +from boa.blockchain.vm.Neo.Runtime import Notify + + +def Main(): + """ + + :return: + """ + m = [1, 2, 3, 4] + + m.remove(1) + + return m diff --git a/boa/tests/src/ConcatTest2.py b/boa/tests/src/ConcatTest2.py new file mode 100644 index 0000000..a12b518 --- /dev/null +++ b/boa/tests/src/ConcatTest2.py @@ -0,0 +1,53 @@ +""" +Data Concatenation Test + +A simple utilise contract that attempts to concatenate 2 values together, disregard of their data types. + +Test Command: + build [FILE_PATH] test 0710 05 False False concat ['lorem','ipsum'] +Example Executions: + testinvoke [CONTRACT_HASH] concat ['lorem','ipsum'] + testinvoke [CONTRACT_HASH] concat ['cloud',9'] + testinvoke [CONTRACT_HASH] concat ['text',b'1010'] +Invalid Examples: + testinvoke [CONTRACT_HASH] concat [true,false] + testinvoke [CONTRACT_HASH] concat ['null',null] +False Positive Examples: + testinvoke [CONTRACT_HASH] concat [0.9,1.23] +""" +from boa.blockchain.vm.Neo.Runtime import Notify +from boa.code.builtins import concat + + +def Main(operation, args): + """ + + :param operation: The name of the operation to perform + :param args: A list of arguments along with the operation + :type operation: str + :type args: list + :return: The result of the operation + :rtype: bytearray + """ + if operation == 'concat': + return do_concat(args) + else: + Notify('unknown operation') + return False + + +def do_concat(args): + """ + + :param args: A list of arguments along with the operation + :type args: list + :return: result of combined values + :rtype: Union[bool, bytearray] + """ + if len(args) > 1: + a = args[0] + b = args[1] + output = concat(a, b) + return output + Notify('invalid argument length') + return False diff --git a/boa/tests/src/LargeArrayStorageTest.py b/boa/tests/src/LargeArrayStorageTest.py new file mode 100644 index 0000000..96f08b1 --- /dev/null +++ b/boa/tests/src/LargeArrayStorageTest.py @@ -0,0 +1,221 @@ +""" +Large Array Storage Test + +A simple utilise contract that allows you to manipulate a stored list for stress tests and GAS cost evaluation. + +Test Command: + build [FILE_PATH] test 0710 05 True False init + +Example Executions: + testinvoke [CONTRACT_HASH] init + testinvoke [CONTRACT_HASH] delete + testinvoke [CONTRACT_HASH] fetch + testinvoke [CONTRACT_HASH] count + testinvoke [CONTRACT_HASH] append_1 + testinvoke [CONTRACT_HASH] append_10 +""" +from boa.blockchain.vm.Neo.Storage import Get, Put, Delete, GetContext +from boa.blockchain.vm.Neo.Runtime import Log, Notify +from boa.code.builtins import concat, list, range, take, substr + +# -- Global variables +KEY = 'test_array' + + +def Main(operation, args): + """ + + :param operation: The name of the operation to perform + :param args: A list of arguments along with the operation + :type operation: str + :type args: list + :return: The result of the operation + :rtype: bytearray + """ + if operation == 'init': + return do_init() + elif operation == 'delete': + return do_delete() + elif operation == 'fetch': + return do_fetch() + elif operation == 'count': + return do_count() + elif operation == 'append_1': + return do_append_1() + elif operation == 'append_10': + return do_append_10() + else: + Notify('unknown operation') + return False + + +def do_init(): + """ + Initialize the storage by setting an empty list. + + :return: indication success execution of the command + :rtype: bool + """ + context = GetContext() + init_list_bytes = serialize_array([]) + Put(context, KEY, init_list_bytes) + return True + + +def do_delete(): + """ + Delete the storage and reset back to its default state. + + :return: indication success execution of the command + :rtype: bool + """ + context = GetContext() + Delete(context, KEY) + return True + + +def do_fetch(): + """ + Fetch current list value from storage. + + :return: the stored list value + :rtype: list + """ + context = GetContext() + list_bytes = Get(context, KEY) + return deserialize_bytearray(list_bytes) + + +def do_count(): + """ + Fetch length of the stored list. + + :return: the stored list value + :rtype: int + """ + context = GetContext() + list_bytes = Get(context, KEY) + item_list = deserialize_bytearray(list_bytes) + return len(item_list) + + +def do_append_1(): + """ + Add 1 item into the list in storage. + + :return: indication success execution of the command + :rtype: bool + """ + context = GetContext() + list_bytes = Get(context, KEY) + item_list = deserialize_bytearray(list_bytes) + item_list.append('single item') + list_length = len(item_list) + Log('new list length:') + Log(list_length) + list_bytes = serialize_array(item_list) + Put(context, KEY, list_bytes) + return True + + +def do_append_10(): + """ + Add 10 items into the list in storage. + + :return: indication success execution of the command + :rtype: bool + """ + context = GetContext() + list_bytes = Get(context, KEY) + item_list = deserialize_bytearray(list_bytes) + addition_list = [ + '1 of 10', '1 of 10', '1 of 10', '1 of 10', '1 of 10', '1 of 10', '1 of 10', '1 of 10', '1 of 10', '1 of 10' + ] + for item in addition_list: + item_list.append(item) + list_length = len(item_list) + Log('new list length:') + Log(list_length) + list_bytes = serialize_array(item_list) + Put(context, KEY, list_bytes) + return True + + +def deserialize_bytearray(data): + + # get length of length + collection_length_length = data[0:1] + + # get length of collection + collection_len = data[1:collection_length_length + 1] + + # create a new collection + new_collection = list(length=collection_len) + + # trim the length data + offset = 1 + collection_length_length + + for i in range(0, collection_len): + + # get the data length length + itemlen_len = data[offset:offset + 1] + + # get the length of the data + item_len = data[offset + 1:offset + 1 + itemlen_len] + + # get the data + item = data[offset + 1 + itemlen_len: offset + 1 + itemlen_len + item_len] + + # store it in collection + new_collection[i] = item + + offset = offset + item_len + itemlen_len + 1 + + return new_collection + + +def serialize_array(items): + + # serialize the length of the list + itemlength = serialize_var_length_item(items) + + output = itemlength + + # now go through and append all your stuff + for item in items: + + # get the variable length of the item + # to be serialized + itemlen = serialize_var_length_item(item) + + # add that indicator + output = concat(output, itemlen) + + # now add the item + output = concat(output, item) + + # return the stuff + return output + + +def serialize_var_length_item(item): + + # get the length of your stuff + stuff_len = len(item) + + # now we need to know how many bytes the length of the array + # will take to store + + # this is one byte + if stuff_len <= 255: + byte_len = b'\x01' + # two byte + elif stuff_len <= 65535: + byte_len = b'\x02' + # hopefully 4 byte + else: + byte_len = b'\x04' + + out = concat(byte_len, stuff_len) + + return out diff --git a/boa/tests/src/NEP5Test.py b/boa/tests/src/NEP5Test.py index d80b802..91ded8c 100644 --- a/boa/tests/src/NEP5Test.py +++ b/boa/tests/src/NEP5Test.py @@ -27,7 +27,7 @@ from boa.blockchain.vm.Neo.TriggerType import Application, Verification from boa.blockchain.vm.Neo.Output import GetScriptHash, GetValue, GetAssetId from boa.blockchain.vm.Neo.Storage import GetContext, Get, Put, Delete - +from boa.blockchain.vm.Neo.Header import GetTimestamp, GetNextConsensus # ------------------------------------------- # TOKEN SETTINGS diff --git a/boa/tests/src/blockchain/AttrTest.py b/boa/tests/src/blockchain/AttrTest.py index eb71693..33167af 100644 --- a/boa/tests/src/blockchain/AttrTest.py +++ b/boa/tests/src/blockchain/AttrTest.py @@ -1,5 +1,7 @@ from boa.blockchain.vm.Neo.Blockchain import GetHeader, GetBlock from boa.blockchain.vm.Neo.Runtime import Notify +from boa.blockchain.vm.Neo.Header import GetTimestamp +from boa.blockchain.vm.Neo.Block import GetTransactions def Main(): diff --git a/boa/tests/src/blockchain/BlockTest.py b/boa/tests/src/blockchain/BlockTest.py index 7e7d907..fd8fc52 100644 --- a/boa/tests/src/blockchain/BlockTest.py +++ b/boa/tests/src/blockchain/BlockTest.py @@ -1,5 +1,6 @@ from boa.blockchain.vm.Neo.Blockchain import GetBlock -from boa.blockchain.vm.Neo.Runtime import Notify, Log +from boa.blockchain.vm.Neo.Runtime import Notify +from boa.blockchain.vm.Neo.Block import GetTransactions def Main(): diff --git a/boa/tests/src/blockchain/ExScriptHashTest.py b/boa/tests/src/blockchain/ExScriptHashTest.py index 63edc50..743b392 100644 --- a/boa/tests/src/blockchain/ExScriptHashTest.py +++ b/boa/tests/src/blockchain/ExScriptHashTest.py @@ -1,6 +1,6 @@ from boa.blockchain.vm.Neo.Blockchain import GetAccount, GetAsset from boa.blockchain.vm.System.ExecutionEngine import GetExecutingScriptHash -from boa.blockchain.vm.Neo.Account import GetBalance +from boa.blockchain.vm.Neo.Account import GetBalance, GetVotes from boa.blockchain.vm.Neo.Runtime import Notify GAS = b'\xe7-(iy\xeel\xb1\xb7\xe6]\xfd\xdf\xb2\xe3\x84\x10\x0b\x8d\x14\x8ewX\xdeB\xe4\x16\x8bqy,`' diff --git a/boa/tests/src/blockchain/HeaderTest.py b/boa/tests/src/blockchain/HeaderTest.py index f4b4537..ed196e6 100644 --- a/boa/tests/src/blockchain/HeaderTest.py +++ b/boa/tests/src/blockchain/HeaderTest.py @@ -1,5 +1,5 @@ from boa.blockchain.vm.Neo.Blockchain import GetHeader -from boa.blockchain.vm.Neo.Header import GetMerkleRoot, GetTimestamp, GetHash, GetVersion +from boa.blockchain.vm.Neo.Header import GetMerkleRoot, GetTimestamp, GetHash, GetVersion, GetNextConsensus from boa.blockchain.vm.Neo.Runtime import Notify, Log diff --git a/boa/tests/src/blockchain/ModulePathTest.py b/boa/tests/src/blockchain/ModulePathTest.py index 1da12e4..6ed01c0 100644 --- a/boa/tests/src/blockchain/ModulePathTest.py +++ b/boa/tests/src/blockchain/ModulePathTest.py @@ -1,5 +1,7 @@ from boa.blockchain.vm.Neo.Runtime import Notify from boa.blockchain.vm.Neo.Blockchain import GetHeader, GetBlock +from boa.blockchain.vm.Neo.Header import GetTimestamp +from boa.blockchain.vm.Neo.Block import GetTransactions from boa.blockchain.vm.Neo.Transaction import * INVOKE_TX_TYPE = b'\xd1' diff --git a/boa/tests/src/blockchain/TXUnspentCoins.py b/boa/tests/src/blockchain/TXUnspentCoins.py index 8062ed2..9394982 100644 --- a/boa/tests/src/blockchain/TXUnspentCoins.py +++ b/boa/tests/src/blockchain/TXUnspentCoins.py @@ -1,7 +1,7 @@ from boa.blockchain.vm.Neo.Transaction import * from boa.blockchain.vm.Neo.Blockchain import GetTransaction from boa.blockchain.vm.Neo.Runtime import Notify -from boa.blockchain.vm.Neo.Output import GetValue +from boa.blockchain.vm.Neo.Output import GetValue, GetScriptHash def Main(txhash): diff --git a/boa/tests/src/objects/stuff/storage_api.py b/boa/tests/src/objects/stuff/storage_api.py index e117899..f0d3fc6 100644 --- a/boa/tests/src/objects/stuff/storage_api.py +++ b/boa/tests/src/objects/stuff/storage_api.py @@ -2,7 +2,7 @@ from boa.blockchain.vm.Neo.Storage import GetContext, Get, Put, Delete -class StorageAPI(): +class StorageAPI(object): ctx = GetContext() diff --git a/boa/tests/src/objects/stuff/things.py b/boa/tests/src/objects/stuff/things.py index 74145ac..68aac96 100644 --- a/boa/tests/src/objects/stuff/things.py +++ b/boa/tests/src/objects/stuff/things.py @@ -1,5 +1,5 @@ -class Awesome(): +class Awesome(object): """ A very basic object """ @@ -20,7 +20,7 @@ def awesome_name(self): return self.myname -class MoreAwesome(): +class MoreAwesome(object): """ A little more complicated object """ diff --git a/docs/source/conf.py b/docs/source/conf.py index 75171a2..e9f1f2b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -67,9 +67,9 @@ # built documents. # # The short X.Y version. -version = '0.2.1' +version = '0.2.2' # The full version, including alpha/beta/rc tags. -release = '0.2.1' +release = '0.2.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 7926d92..57b20e0 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.2.1', + version='0.2.2', description='A Python compiler for the Neo Virtual Machine', long_description=long_description,