From f59f9f6b2d8fc0c6b85901bdb552ffa40b17fa09 Mon Sep 17 00:00:00 2001 From: Bryant Date: Wed, 18 Oct 2017 16:28:07 -0400 Subject: [PATCH 001/162] Added get_logs method to return a list of logs with optional filtering; changed get_log fixture to use last entry in get_logs list for specific log name --- tests/setup_transaction_tests.py | 33 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/tests/setup_transaction_tests.py b/tests/setup_transaction_tests.py index 1ef0ce7a25..c7a50a602a 100644 --- a/tests/setup_transaction_tests.py +++ b/tests/setup_transaction_tests.py @@ -103,26 +103,25 @@ def get_contract(source_code, *args, **kwargs): curve_order = 21888242871839275222246405745257275088548364400416034343698204186575808495617 +def get_logs(receipt, contract, event_name=None): + contract_log_ids = contract.translator.event_data.keys() # All the log ids contract has + # All logs originating from contract, and matching event_name (if specified) + logs = [log for log in receipt.logs \ + if log.topics[0] in contract_log_ids and \ + log.address == contract.address and \ + (not event_name or \ + contract.translator.event_data[log.topics[0]]['name'] == event_name)] + assert len(logs) > 0, "No logs in last receipt" + + # Return all events decoded in the receipt + return [contract.translator.decode_event(log.topics, log.data) for log in logs] + @pytest.fixture def get_log(): def get_log(tester, contract, event_name): - event_ids_w_name = [k for k, v in \ - contract.translator.event_data.items() if v["name"] == event_name] - assert len(event_ids_w_name) == 1, \ - "Contract doesn't have event {}!".format(event_name) - event_id = event_ids_w_name[0] - - # Get the last logged event - logs = tester.s.head_state.receipts[-1].logs[-1] - - # Ensure it has the event we are looking to decode - assert logs.address == contract.address, \ - "This contract didn't originate the last event!" - assert logs.topics[0] == event_id, \ - "The last event wasn't {}!".format(event_name) - - # Return the decoded event data - return contract.translator.decode_event(logs.topics, logs.data) + receipt = tester.s.head_state.receipts[-1] # Only the receipts for the last block + # Get last log event with correct name and return the decoded event + return get_logs(receipt, contract, event_name=event_name)[-1] return get_log @pytest.fixture From 543d6f4b0b7f51d51dd81df2e615ba6cf6f7df79 Mon Sep 17 00:00:00 2001 From: chgue Date: Mon, 23 Oct 2017 22:03:19 +0200 Subject: [PATCH 002/162] WIP overhaul of types --- docs/types.rst | 303 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 246 insertions(+), 57 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 5265f56bdf..46a83f1a40 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -2,9 +2,9 @@ .. _types: -***** +##### Types -***** +##### Viper is a statically typed language, which means that the type of each variable (state and local) needs to be specified or at least known at @@ -14,8 +14,9 @@ to form complex types. In addition, types can interact with each other in expressions containing operators. +*********** Value Types -=========== +*********** The following types are also called value types because variables of these types will always be passed by value, i.e. they are always copied when they @@ -23,81 +24,269 @@ are used as function arguments or in assignments. .. index:: ! bool, ! true, ! false -Booleans --------- +Boolean +======= +**Keyword:** ``bool`` -``bool``: The possible values are constants ``true`` and ``false``. - -Operators: - -* ``not`` (logical negation) -* ``and`` (logical conjunction, "&&") -* ``or`` (logical disjunction, "||") -* ``==`` (equality) -* ``not ... == ... ``, ``!=`` (inequality) - -The operators ``or`` and ``and`` apply the common short-circuiting rules. This means that in the expression ``f(x) or g(y)``, if ``f(x)`` evaluates to ``true``, ``g(y)`` will not be evaluated even if it may have side-effects. - -.. index:: ! uint, ! int, ! integer +A Boolean is a type to store a logical/truth value. +Values +------ +The only possible values are the constants ``true`` and ``false``. + +Operators +--------- + +==================== =================== +Operator Description +==================== =================== +``f(x) not g(y)`` Logical negation +``f(x) and g(y)`` Logical conjunction +``f(x) or g(y)`` Logical disjunction +``f(x) == g(y)`` Equality +``f(x) != g(y)`` Inequality +==================== =================== + +The operators ``or`` and ``and`` apply the common short-circuiting rules: +:: + #Short-circuiting + return false and foo() + #Returns false without calling foo() since it is not necessary for the result + return true or bar() + #Returns true without calling bar() since it is not necessary for the result -Integers --------- +.. index:: ! num, ! int, ! integer +Signed Integer (128 bit) +======================== +**Keyword:** ``num`` -``num``: equivalent to``int128``, a signed integer strictly between -2\*\*127 and 2\*\*127-1. +A signed integer (128 bit) is a type to store positive and negative integers. -Operators: +Values +------ +Signed integer values between -2\ :sup:`127` and (2\ :sup:`127` - 1). + +Operators +--------- +Comparisons +^^^^^^^^^^^ +Comparisons return a boolean value. + +========== ================ +Operator Description +========== ================ +``x < y`` Less than +``x <= y`` Less or equal +``x == y`` Equals +``x != y`` Does not equal +``x >= y`` Greater or equal +``x > y`` Greater than +========== ================ +``x`` and ``y`` must be of the type ``num``. + +Arithmetic operators +^^^^^^^^^^^^^^^^^^^^ -* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) -* Arithmetic operators: ``+``, ``-``, unary ``-``, unary ``+``, ``*``, ``/``, ``%`` (remainder) +============= ====================== +Operator Description +============= ====================== +``x + y`` Addition +``x - y`` Subtraction +``-x`` Unary minus/Negation +``x * y`` Multiplication +``x / y`` Divison +``x**y`` Exponentiation +``x % y`` Modulo +``min(x, y)`` Minimum +``max(x, y)`` Maximum +============= ====================== +``x`` and ``y`` must be of the type ``num``. + +Conversion +---------- +A ``num`` can be converted to a ``num256`` with the function ``as_num256(x)``, where ``x`` is of the type ``num``. +Conversly, a ``num256`` can be converted to a ``num`` with the function ``as_num128(x)``, where ``x`` is of the type ``num256``. + +.. index:: ! unit, ! num256 +Unsigned Integer (256 bit) +========================== +**Keyword:** ``num256`` + +An unsigned integer (256 bit) is a type to store non-negative integers. + +Values +------ +Integer values between 0 and (2\ :sup:`257`-1). +.. note:: + Integer literals are always interpreted as ``num``. In order to assign a literal to a ``num256`` use ``as_num256(_literal)``. + +Operators +--------- +Comparisons +^^^^^^^^^^^ +Comparisons return a boolean value. + +=================== ================ +Operator Description +=================== ================ +``num256_lt(x, y)`` Less than +``num256_le(x, y)`` Less or equal +``x == y`` Equals +``x != y`` Does not equal +``num256_ge(x, y)`` Greater or equal +``num256_gt(x, y)`` Greater than +=================== ================ +``x`` and ``y`` must be of the type ``num256``. + +Arithmetic operators +^^^^^^^^^^^^^^^^^^^^ +======================= ====================== +Operator Description +======================= ====================== +``num256_add(x, y)`` Addition +``num256_sub(x, y)`` Subtraction +``num256_addmod(x, y)`` Modular addition +``num256_mul(x, y)`` Multiplication +``num256_mulmod(x, y)`` Modular multiplication +``num256_div(x, y)`` Divison +``num256_exp(x, y)`` Exponentiation +``num256_mod(x, y)`` Modulo +``min(x, y)`` Minimum +``max(x, y)`` Maximum +======================= ====================== +``x`` and ``y`` must be of the type ``num256``. + +Bitwise operators +^^^^^^^^^^^^^^^^^ + +===================== ============= +Operator Description +===================== ============= +``bitwise_and(x, y)`` AND +``bitwise_not(x, y)`` NOT +``bitwise_or(x, y)`` OR +``bitwise_xor(x, y)`` XOR +``shift(x, _shift)`` Bitwise Shift +===================== ============= +``x`` and ``y`` must be of the type ``num256``. ``_shift`` must be of the type ``num``. + +.. note:: + Positive ``_shift`` equals a left shift; negative ``_shift`` equals a right shift. + Values shifted above/below the most/least significant bit get discarded. + +Conversion +---------- +A ``num256`` can be converted to a ``num`` with the function ``as_num128(x)``, where ``x`` is of the type ``num256``. +Conversly, a ``num`` can be converted to a ``num256`` with the function ``as_num256(x)``, where ``x`` is of the type ``num``. + Decimals --------- -``decimal``: a decimal fixed point value with the integer component represented as a ``num`` and the fractional component supporting up to ten decimal places. - - -Time ------ -``timestamp``: a timestamp value with a base unit of one second, represented as a ``num``. - -``timedelta``: a number of seconds (note: two timedeltas can be added together, as can a timedelta and a timestamp, but not two timestamps), represented as a ``num``. +======== +**Keyword:** ``decimal`` +A decimal is a type to store a decimal fixed point value. -Value +Values ------ -``wei_value``: an amount of `ether `_ with a base unit of one wei, represented as a ``num``. - -``currency_value``: represents an amount of currency and should be used to represent assets where ether is traded for value, represented as a ``num``. - +A value with a precision of 10 decimal places between -2\ :sup:`127` and (2\ :sup:`127` - 1). + +Operators +--------- +Comparisons +^^^^^^^^^^^ +Comparisons return a boolean value. + +========== ================ +Operator Description +========== ================ +``x < y`` Less than +``x <= y`` Less or equal +``x == y`` Equals +``x != y`` Does not equal +``x >= y`` Greater or equal +``x > y`` Greater than +========== ================ +``x`` and ``y`` must be of the type ``decimal``. + +Arithmetic operators +^^^^^^^^^^^^^^^^^^^^ +============= ========================================== +Operator Description +============= ========================================== +``x + y`` Addition +``x - y`` Subtraction +``-x`` Unary minus/Negation +``x * y`` Multiplication +``x / y`` Divison +``x % y`` Modulo +``min(x, y)`` Minimum +``max(x, y)`` Maximum +``floor(x)`` Largest integer <= ``x``. Returns ``num``. +============= ========================================== +``x`` and ``y`` must be of the type ``decimal``. .. _address: - Address -------- +======= +**Keyword:** ``address`` -``address``: Holds an Ethereum address (20 byte value). +The address type holds an Ethereum address. +Values +------ +An Address type can hold an Ethereum address which equates to 20 bytes/160 bits. Returns in hexadecimal notation with a leading ``0x``. .. _members-of-addresses: +Members +^^^^^^^ + +============ =================================================== +Member Description +============ =================================================== +``balance`` Query balance of an address. Returns ``wei_value``. +``codesize`` Query the code size of an address. Returns ``num``. +============ =================================================== +Syntax as follows: ``_address.``, where ``_address`` is of the type ``address`` and ```` is one of the above keywords. + +Unit Types +========== +Viper allows the definition of types with a discrete unit such as e.g. meters, seconds, wei, ... . These types may only be based on either ``num`` or ``decimal``. +Viper has multiple unit types built in, which are the following: + +============= ===== ========= ========================== +Time +----------------------------------------------------------- +Keyword Unit Base type Description +============= ===== ========= ========================== +``timestamp`` 1 sec ``num`` Represents a point in time +``timedelta`` 1 sec ``num`` A number of seconds +============= ===== ========= ========================== -Members of Addresses -^^^^^^^^^^^^^^^^^^^^ - -* ``balance`` and ``send`` - -It is possible to query the balance of an address using the property ``balance`` -and to send Ether (in units of wei) to an address using the ``send`` function: - -:: - - x: address - - def foo(x: address): - if (x.balance < 10 and self.balance >= 10): - x.send(10) - +.. note:: + Two ``timedelta`` can be added together, as can a ``timedelta`` and a ``timestamp``, but not two ``timestamps``. + +=================== =========== ========= ==================================================================================== +Currency +--------------------------------------------------------------------------------------------------------------------------------- +Keyword Unit Base type Description +=================== =========== ========= ==================================================================================== +``wei_value`` 1 wei ``num`` An amount of `Ether `_ in wei +``currency_value`` 1 currency ``num`` An amount of currency +``currency1_value`` 1 currency1 ``num`` An amount of currency1 +``currency2_value`` 1 currency2 ``num`` An amount of currency2 +=================== =========== ========= ==================================================================================== + +Conversion +---------- +The unit of a unit type may be stripped with the function ``as_unitless_number(_unitType)``, where ``_unitType`` is a unit type. The returned value is then either a ``num`` +or a ``decimal``, depending on the base type. + +################# +TODO from here on +################# +Todo: bytes32 and reference types; revist conversion between num/num256/bytes32 .. index:: byte array, bytes32 From e436f140a0f9202eef0677af993df7554f420061 Mon Sep 17 00:00:00 2001 From: Edward He Date: Thu, 19 Oct 2017 13:24:05 -0400 Subject: [PATCH 003/162] Draft of viper-by-example. --- docs/index.rst | 20 +- docs/viper-by-example.rst | 628 ++++++++++++++++++++++++++++++++++++++ setup.cfg | 5 +- 3 files changed, 640 insertions(+), 13 deletions(-) create mode 100644 docs/viper-by-example.rst diff --git a/docs/index.rst b/docs/index.rst index 110887988e..f9038a3f79 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,10 +15,10 @@ Principles and Goals * **Security:** It should be possible and natural to build secure smart-contracts in Viper. * **Language and compiler simplicity:** The language and the compiler implementation should strive to be simple. -* **Auditability:** Viper code should be maximally human-readable. Furthermore, it should be maximally difficult to write misleading code. Simplicity for the reader - is more important than simplicity for the writer, and simplicity for readers with low prior experience with Viper (and low prior experience with programming in +* **Auditability:** Viper code should be maximally human-readable. Furthermore, it should be maximally difficult to write misleading code. Simplicity for the reader + is more important than simplicity for the writer, and simplicity for readers with low prior experience with Viper (and low prior experience with programming in general) is particularly important. - + Because of this Viper aims to provide the following features: * **Bounds and overflow checking:** On array accesses as well as on arithmetic level. @@ -30,18 +30,18 @@ Because of this Viper aims to provide the following features: Following the principles and goals, Viper **does not** provide the following features: -* **Modifiers**: For example in Solidity you can define a ``function foo() mod1 { ... }``, where ``mod1`` can be defined elsewhere in the code to include a check that is done before execution, - a check that is done after execution, some state changes, or possibly other things. Viper does not have this, because it makes it too easy to write misleading code. ``mod1`` just looks - too innocuous for something that could add arbitrary pre-conditions, post-conditions or state changes. Also, it encourages people to write code where the execution jumps around the file, +* **Modifiers**: For example in Solidity you can define a ``function foo() mod1 { ... }``, where ``mod1`` can be defined elsewhere in the code to include a check that is done before execution, + a check that is done after execution, some state changes, or possibly other things. Viper does not have this, because it makes it too easy to write misleading code. ``mod1`` just looks + too innocuous for something that could add arbitrary pre-conditions, post-conditions or state changes. Also, it encourages people to write code where the execution jumps around the file, harming auditability. The usual use case for a modifier is something that performs a single check before execution of a program; our recommendation is to simply inline these checks as asserts. -* **Class inheritance:** Class inheritance requires people to jump between multiple files to understand what a program is doing, and requires people to understand the rules of precedence in case of conflicts +* **Class inheritance:** Class inheritance requires people to jump between multiple files to understand what a program is doing, and requires people to understand the rules of precedence in case of conflicts ("Which class's function 'X' is the one that's actually used?"). Hence, it makes code too complicated to understand which negatively impacts auditability. * **Inline assembly:** Adding inline assembly would make it no longer possible to search for a variable name in order to find all instances where that variable is read or modified. -* **Operator overloading:** Operator overloading makes writing misleading code possible. For example "+" could be overloaded so that it executes commands the are not visible at first glance, such as sending funds the +* **Operator overloading:** Operator overloading makes writing misleading code possible. For example "+" could be overloaded so that it executes commands the are not visible at first glance, such as sending funds the user did not want to send. * **Recursive calling:** Recursive calling makes it impossible to set an upper bound on gas limits, opening the door for gas limit attacks. * **Infinite-length loops:** Similar to recurisve calling, infinite-length loops make it impossible to set an upper bound on gas limits, opening the door for gas limit attacks. -* **Binary fixed point:** Decimal fixed point is better, because any decimal fixed point value written as a literal in code has an exact representation, whereas with binary fixed point approximations are often required +* **Binary fixed point:** Decimal fixed point is better, because any decimal fixed point value written as a literal in code has an exact representation, whereas with binary fixed point approximations are often required (e.g. (0.2)\ :sub:`10` = (0.001100110011...)\ :sub:`2`, which needs to be truncated), leading to unintuitive results, e.g. in Python 0.3 + 0.3 + 0.3 + 0.1 != 1. ******************************** @@ -61,7 +61,7 @@ Glossary installing-viper.rst compiling-a-contract.rst + viper-by-example.rst viper-in-depth.rst contributing.rst frequently-asked-questions.rst - diff --git a/docs/viper-by-example.rst b/docs/viper-by-example.rst new file mode 100644 index 0000000000..57a26653bd --- /dev/null +++ b/docs/viper-by-example.rst @@ -0,0 +1,628 @@ +################### +Viper by Example +################### + +.. index:: auction;open, open auction + +******************* +Simple Open Auction +******************* + +.. _simple_auction: + +As an introductory example of a smart contract written in Viper, we will begin +with a simple open auction contract. As we dive into the code, +it is important to remember that all Viper syntax is valid Python3 syntax, +however not all Python3 functionality is available in Viper. + +In this contract, we will be looking at a simple open auction contract where +participants can submit bids during a limited time period. When the auction +period ends, a predetermined beneficiary will receive the amount of the highest +bid. + +.. literalinclude:: ../examples/auctions/simple_open_auction.v.py + :language: python + :linenos: + +As you can see, this example only has a constructor, two methods to call, and +a few variables to manage the contract state. Believe it or not, this is all we +need for a basic implementation of an auction smart contract. + +Let's get started! + +.. literalinclude:: ../examples/auctions/simple_open_auction.v.py + :language: python + :lines: 3-14 + +We begin by declaring a few variables to keep track of our contract state. +We initialize a global variable ``beneficiary`` by calling ``public`` on the +datatype ``address``. The ``beneficiary`` will be the receiver of money from +the highest bidder. We also initialize the variables ``auction_start`` and +``auction_end`` with the datatype ``timestamp`` to manage the open auction +period and ``highest_bid`` with datatype ``wei_value``, the smallest +denomination of ether, to manage auction state. The variable ``ended`` is a +boolean to determine whether the auction is officially over. + +You may notice the all of the variables being passed into the ``public`` +function. By declaring the variable *public*, the variable is +callable by external contracts. Initializing the variables without the ``public`` +function defaults to a private declaration and thus only accessible to methods +within the same contract. The ``public`` function additionally creates a +‘getter’ function for the variable, accessible with a call such as +``self.get_beneficiary(some_address)``. + +Now, the constructor. + +.. literalinclude:: ../examples/auctions/simple_open_auction.v.py + :language: python + :lines: 16-22 + +The contract is initialized with two arguments: ``_beneficiary`` of type +``address`` and ``bidding_time`` with type ``timedelta``, the time difference +between the start and end of the auction. We then store these two pieces of +information into the contract variables ``self.beneficiary`` and +``self.auction_end``. Notice that we have access to the current time by +calling ``block.timestamp``. ``block`` is an object available within any Viper +contract and provides information about the block at the time of calling. +Similar to ``block``, another important object available to us within the +contract is ``msg``, which provides information on the method caller as we will +soon see. + +With initial setup out of the way, lets look at how our users can make bids. + +.. literalinclude:: ../examples/auctions/simple_open_auction.v.py + :language: python + :lines: 24-38 + +The ``@payable`` decorator will allow a user to send some ethers to the +contract in order to call the decorated method. In this case, a user wanting +to make a bid would call the ``bid()`` method while sending an amount equal +to their desired bid (not including gas fees). When calling any method within a +contract, we are provided with a built-in variable ``msg`` and we can access +the public address of any method caller with ``msg.sender``. Similarly, the +amount of ether a user sends can be accessed by calling ``msg.sender``. + +.. warning:: ``msg.sender`` will change between internal function calls so that + if you're calling a function from the outside, it's correct for the first + function call. But then, for the function calls after, ``msg.sender`` will + reference the contract itself as opposed to the sender of the transaction. + +Here, we first check whether the current time is before the auction's end time +using the ``assert`` function which takes any boolean statement. We also check +to see if the new bid is greater than the highest bid. If both ``assert`` +statements pass, we continue to the next lines; otherwise, the ``bid()`` method +will throw an error and revert the transaction. If the two ``assert`` statements +the check that the previous bid is not equal to zero pass, we can safely +conclude that we have a valid new highest bid. We will send back the previous +``highest_bid`` to the previous ``highest_bidder`` and set our new +``highest_bid`` and ``highest_bidder``. + +.. literalinclude:: ../examples/auctions/simple_open_auction.v.py + :language: python + :lines: 41-67 + +With the ``auction_end()`` method, we check whether our current time is past +the ``auction_end`` time we set upon initialization of the contract. We also +check that ``self.ended`` had not previously been set to True. We do this +to prevent any calls to the method if the auction had already ended, +which could potentially be malicious if the check had not been made. +We then officially end the auction by setting ``self.ended`` to ``True`` +and sending the highest bid amount to the beneficiary. + +And there you have it - an open auction contract. Of course, this is a +simplified example with barebones functionality and can be improved. +Hopefully, this has provided some insight to the possibilities of Viper. +As we move on to exploring more complex examples, we will encounter more +design patterns and features of the Viper language. + + +And of course, no smart contract tutorial is complete without a note on +security. + +.. note:: It's always important to keep security in mind when designing a smart +contract. As any application becomes more complex, the greater the potential for +introducing new risks. Thus, it's always good practice to keep contracts as +readable and simple as possible. + +Whenever you're ready, let's turn it up a notch in the next example. + + +.. index:: purchases + +********************* +Safe Remote Purchases +********************* + +.. _safe_remote_purchases: + + +In this example, we have an escrow contract implementing a system for a trustless +transaction between a buyer and a seller. In this system, a seller posts an item +for sale and makes a deposit to the contract of twice the item's ``value``. At +this moment, the contract has a balance of 2 * ``value``. The seller can reclaim +the deposit and close the sale as long as a buyer has not yet made a purchase. +If a buyer is interested in making a purchase, they would make a payment and +submit an equal amount for deposit (totaling 2 * ``value``) into the contract +and locking the contract from further modification. At this moment, the contract +has a balance of 4 * ``value`` and the seller would send the item to buyer. Upon +the buyer's receipt of the item, the buyer will mark the item as received in the +contract, thereby returning the buyer's deposit (not payment), releasing the +remaining funds to the seller, and completing the transaction. + +There are certainly others ways of designing a secure escrow system with less +overhead for both the buyer and seller, but for the purpose of this example, +we want to explore one way how an escrow system can be implemented trustlessly. + +Let's go! + +.. literalinclude:: ../examples/safe_remote_purchase/safe_remote_purchase.v.py + :language: python + :linenos: + +This is also a moderately short contract, however a little more complex in +logic. Let's break down this contract bit by bit. + +.. literalinclude:: ../examples/safe_remote_purchase/safe_remote_purchase.v.py + :language: python + :lines: 10-13 + +Like the other contracts, we begin by declaring our global variables public with +their respective datatypes. Remember that the ``public`` function allows the +variables to be *readable* by an external caller, but not *writeable*. + +.. literalinclude:: ../examples/safe_remote_purchase/safe_remote_purchase.v.py + :language: python + :lines: 18-23 + +With a ``@payable`` decorator on the constructor, the contract creator will be +required to make an initial deposit equal to twice the item's ``value`` to +initialize the contract, which will be later returned. This is in addition to +the gas fees needed to deploy the contract on the blockchain, which is not +returned. We ``assert`` that the deposit is divisible by 2 to ensure that the +seller deposited a valid amount. The constructor stores the item's value +in the contract variable ``self.value`` and saves the contract creator into +``self.seller``. The contract variable ``self.unlocked`` is initialized to +``True``. + +.. literalinclude:: ../examples/safe_remote_purchase/safe_remote_purchase.v.py + :language: python + :lines: 25-28 + +The ``abort()`` method is a method only callable by the seller and while the +contract is still ``unlocked`` - meaning it is callable only prior to any buyer +making a purchase. As we will see in the ``purchase()`` method that when +a buyer calls the ``purchase()`` method and sends a valid amount to the contract, +the contract will be locked and the seller will no longer be able to call +``abort()``. + +When the seller calls ``abort()`` and if the ``assert`` statements pass, the +contract will call the ``selfdestruct()`` function and refunds the seller and +subsequently destroys the contract. + +.. literalinclude:: ../examples/safe_remote_purchase/safe_remote_purchase.v.py + :language: python + :lines: 30-35 + +Like the constructor, the ``purchase()`` method has a ``@payable`` decorator, +meaning it can be called with a payment. For the buyer to make a valid +purchase, we must first ``assert`` that the contract's unlocked property is +``False`` and that the amount sent is equal to twice the item's value. We then +set the buyer to the ``msg.sender`` and lock the contract. At this point, the +contract has a balance equal to 4 times the item value and the seller must +send the item to the buyer. + +.. literalinclude:: ../examples/safe_remote_purchase/safe_remote_purchase.v.py + :language: python + :lines: 37-41 + +Finally, upon the buyer's receipt of the item, the buyer can confirm their +receipt by calling the ``received()`` method to distribute the funds as +intended - the seller receives 3/4 of the contract balance and the buyer +receives 1/4. + +By calling ``received()``, we begin by checking that the contract is indeed +locked, ensuring that a buyer had previously paid. We also ensure that this +method is only callable by the buyer. If these two ``assert`` statements pass, +we refund the buyer their initial deposit and send the seller the remaining +funds. The contract is finally destroyed and the transaction is complete. + +Whenever we’re ready, let’s move on to the next example. + + +.. index:: crowdfund + +********* +Crowdfund +********* + +.. _crowdfund: + +Now, let's explore a straightforward example for a crowdfunding contract where +prospective participants can contribute funds to a campaign. If the total +contribution to the campaign reaches or surpasses a predetermined funding goal, +the funds will be sent to the beneficiary at the end of the campaign deadline. +Participants will be refunded their respective contributions if the total +funding does not reach its target goal. + + +.. literalinclude:: ../examples/crowdfund.v.py + :language: python + :linenos: + +Most of this code should be relatively straightforward after going through our +previous examples. Let's dive right in. + +.. literalinclude:: ../examples/crowdfund.v.py + :language: python + :lines: 1-7 + +Like other examples, we begin by initiating our variables - except this time, +we're not calling them with the ``public`` function. Variables initiated this +way are, by default, private. + +..note :: +Unlike the existence of the function ``public()``, there is no equivalent +``private()`` function. Variables simply default to private if initiated +without the ``public()`` function. + +The ``funders`` variable is initiated as a mapping where the key is a number, +and the value is a struct representing the contribution of each participant. +This struct contains each participant's public address and their respective +value contributed to the fund. The key corresponding to each struct in the +mapping will be represented by the variable ``nextFunderIndex`` which is +incremented with each additional contributing participant. Variables initialized +with ``num`` type without an explicit value, such as ``nextFunderIndex``, +defaults to ``0``. The ``beneficiary`` will be the final receiver of the funds +once the crowdfunding period is over - as determined by the ``deadline`` and +``timelimit`` variables. The ``goal`` variable is the target total contribution +of all participants. ``refundIndex`` is a variable for bookkeeping purposes in +order to avoid gas limit issues in the scenario of a refund. + + +.. literalinclude:: ../examples/crowdfund.v.py + :language: python + :lines: 9-14 + +Our constructor function takes 3 arguments: the beneficiary's address, the goal +in wei value, and the difference in time from start to finish of the +crowdfunding. We initialize the arguments as contract variables with their +corresponding names. Additionally, a ``self.deadline`` is initialized to set +a definitive end time for the crowdfunding period. + +Now lets take a look at the method of how a person can participate in the +crowdfund. + +.. literalinclude:: ../examples/crowdfund.v.py + :language: python + :lines: 16-22 + +Once again, we see the ``@payable`` decorator on a method, which allows a +person to send some ether along with a call to the method. In this case, +the ``participate()`` method accesses the sender's address with ``msg.sender`` +and the corresponding amount sent with ``msg.value``. This information is stored +into a struct and then saved into the ``funders`` mapping with +``self.nextFunderIndex`` as the key. As more participants are added to the +mapping, ``self.nextFunderIndex`` increments appropriately to properly index +each participant. + +.. literalinclude:: ../examples/crowdfund.v.py + :language: python + :lines: 24-27 + +The ``finalize()`` method is used to complete the crowdfunding process. However, +to complete the crowdfunding, the method first checks to see if the crowdfunding +period is over and that the balance has reached/passed its set goal. If those +two conditions pass, the contract calls the ``selfdestruct()`` function and +sends the collected funds to the beneficiary. + +.. note:: Notice that we have access to the total amount sent to the contract by +calling ``self.balance``, a variable we never explicitly set. Similar to ``msg`` +and ``block``, ``self.balance`` is a variable thats comes free in all Viper +contracts. + +We can finalize the campaign if all goes well, but what happens if the +crowdfunding campaign isn't successful? We're going to need a way to refund +all the participants. + +.. literalinclude:: ../examples/crowdfund.v.py + :language: python + :lines: 29-40 + +In the ``refund()`` method, we first check that the crowdfunding period is +indeed over and that the total collected balance is less than the ``goal`` with +the ``assert`` statement . If those two conditions pass, we then loop through +every participant and call ``send()`` to send each participant their respective +contribution. For the sake of gas limits, we group the number of contributors +in batches of 30 and refund them one at a time. Unfortunately, if there's a +large number of of participants, multiple calls to ``refund()`` may be +necessary. + +One thing to note about the ``send()`` method is that it costs gas. Thus, +refunding each participant will deduct gas from the total balance of the +contract. This creates a big problem in that the last person to be refunded +bears the burden of paying the gas cost for all refunds. There are many ways +we can redesign this contract to be more fair to all participants in the case +of a refund. One potential way we can go about this is that the beneficiary or +the campaign owner set aside some initial funds during contract initialization to +bear the burden of the cost. But this also has the drawback of the not knowing +how much to set aside since the number of participants is unpredictable at the +time of contract initialization. + +As an exercise, can we think of another way to design this contract to shares +the refunding cost fairly among all participants? *Hint: Can we have the +participants come collect their fund individually instead of having the +contract batch refunding all participants?* + + +.. index:: voting, ballot + +****** +Voting +****** + +In this contract, we will implement a system for participants to vote on a list +of proposals. The chairperson of the contract will be able to give each +participant the right to vote and each participant may choose to vote or +delegate their vote to another voter. Finally, a winning proposal will be +determined upon calling the ``winning_proposals()`` method, which iterates through +all the proposals and returns the one with the greatest number of votes. + + +.. literalinclude:: ../examples/voting/ballot.v.py + :language: python + :linenos: + +As we can see, this is contract of moderate length which we will dissect +section by section. Let’s begin! + +.. literalinclude:: ../examples/voting/ballot.v.py + :language: python + :lines: 3-21 + +The variable ``voters`` is initialized as a mapping where the key is +the voter’s public address and the value is a struct describing the +voter’s properties: ``weight``, ``voted``, ``delegate``, and ``vote``, along +with their respective datatypes. + +Similarly, the ``proposals`` variable is initialized as a ``public`` mapping +with ``num`` as the key’s datatype and a struct to represent each proposal +with the properties ``name`` and ``vote_count``. Like our last example, we can +access any value by key’ing into the mapping with a number just as one would +with an index in an array. + +Then, ``voter_count`` and ``chairperson`` are initialized as ``public`` with +their respective datatypes. + +Let’s move onto the constructor. + +.. literalinclude:: ../examples/voting/ballot.v.py + :language: python + :lines: 26-34 + +.. warning:: Both ``msg.sender`` and ``msg.balance`` change between internal + function calls so that if you're calling a function from the outside, it's + correct for the first function call. But then, for the function calls after, + ``msg.sender`` and ``msg.balance`` reference the contract itself as opposed + to the sender of the transaction. + +In the constructor, we hard-coded the contract to accept an +array argument of exactly two proposal names of type ``bytes32`` for the contracts +initialization. Because upon initialization, the ``__init__()`` method is called +by the contract creator, we have access to the contract creator’s address with +``msg.sender`` and store it in the contract variable ``self.chairperson``. We +also initialize the contract variable ``self.voter_count`` to zero to initially +represent the number of votes allowed. This value will be incremented as each +participant in the contract is given the right to vote by the method +``give_right_to_vote()``, which we will explore next. We loop through the two +proposals from the argument and insert them into ``proposals`` mapping with +their respective index in the original array as its key. + +Now that the initial setup is done, lets take a look at the functionality. + +.. literalinclude:: ../examples/voting/ballot.v.py + :language: python + :lines: 36-46 + +We need a way to control who has the ability to vote. The method +``give_right_to_vote()`` is a method callable by only the chairperson by taking +a voter address and granting it the right to vote by incrementing the voter's +``weight`` property. We sequentially check for 3 conditions using ``assert``. +The ``assert not`` function will check for falsy boolean values - +in this case, we want to know that the voter has not already voted. To represent +voting power, we will set their ``weight`` to ``1`` and we will keep track of the +total number of voters by incrementing ``voter_count``. + +.. literalinclude:: ../examples/voting/ballot.v.py + :language: python + :lines: 48-71 + +In the method ``delegate``, firstly, we check to see that ``msg.sender`` has not +already voted and secondly, that the target delegate and the ``msg.sender`` are +not the same. Voters shouldn’t be able to delegate votes to themselves. We, +then, loop through all the voters to determine whether the person delegate to +had further delegated their vote to someone else in order to follow the +chain of delegation. We then mark the ``msg.sender`` as having voted if they +delegated their vote. We increment the proposal’s ``vote_count`` directly if +the delegate had already voted or increase the delegate’s vote ``weight`` +if the delegate has not yet voted. + +.. literalinclude:: ../examples/voting/ballot.v.py + :language: python + :lines: 73-82 + +Now, let’s take a look at the logic inside the ``vote()`` method, which is +surprisingly simple. The method takes the key of the proposal in the ``proposals`` +mapping as an argument, check that the method caller had not already voted, +sets the voter’s ``vote`` property to the proposal key, and increments the +proposals ``vote_count`` by the voter’s ``weight``. + +With all the basic functionality complete, what’s left is simply returning +the winning proposal. To do this, we have two methods: ``winning_proposal()``, +which returns the key of the proposal, and ``winner_name()``, returning the +name of the proposal. Notice the ``@constant`` decorator on these two methods. +We do this because the two methods only read the blockchain state and do not +modify it. Remember, reading the blockchain state is free; modifying the state +costs gas. By having the ``@constant`` decorator, we let the EVM know that this +is a read-only function and we benefit by saving gas fees. + +.. literalinclude:: ../examples/voting/ballot.v.py + :language: python + :lines: 84-93 + +The ``winning_proposal()`` method returns the key of proposal in the ``proposals`` +mapping. We will keep track of greatest number of votes and the winning +proposal with the variables ``winning_vote_count`` and ``winning_proposal``, +respectively by looping through all the proposals. + +.. literalinclude:: ../examples/voting/ballot.v.py + :language: python + :lines: 95-100 + +And finally, the ``winner_name()`` method returns the name of the proposal by +key’ing into the ``proposals`` mapping with the return result of the +``winning_proposal()`` method. + +And there you have it - a voting contract. Currently, many transactions +are needed to assign the rights to vote to all participants. As an exercise, +can we try to optimize this? + +Now that we're familiar with basic contracts. Let's step up the difficulty. + +.. index:: stock;company, company stock + +************* +Company Stock +************* + +.. _company_stock: + +This contract is just a tad bit more thorough than the ones we've previously +encountered. In this example, we are going to look at a comprehensive contract +that manages the holdings of all shares of a company. The contract allows for +a person to buy, sell, and transfer shares of a company as well as allowing for +the company to pay a person in ether. The company, upon initialization of the +contract, holds all shares of the company at first but can sell them all. + +Let's get started. + +.. literalinclude:: ../examples/stock/company.v.py + :language: python + :linenos: + +The contract contains a number of methods that modify the contract state as +well as a few 'getter' methods to read it. As always, we begin by initiating +our variables. + +.. literalinclude:: ../examples/stock/company.v.py + :language: python + :lines: 1-7 + +We initiate the ``company`` variable to be of type ``address`` that's public. +The ``total_shares`` variable is of type ``currency_value``, which in this case +represents the total available shares of the company. The ``price`` variable +represents the wei value of a share and ``holdings`` is a mapping that maps an +address to the number of shares the address owns. + +.. literalinclude:: ../examples/stock/company.v.py + :language: python + :lines: 9-21 + +In the constructor, we set up the contract to check for valid inputs during +the initialization of the contract via the two ``assert`` statements. If the +inputs are valid, the contract variables are set accordingly and the +company's address is initialized to hold all shares of the company in the +``holdings`` mapping. + +.. literalinclude:: ../examples/stock/company.v.py + :language: python + :lines: 23-25 + +We will be seeing a few ``@constant`` decorators in this contract - which is +used to decorate methods that simply read the contract state or return a simple +calculation on the contract state without modifying it. Remember, reading the +blockchain is free, writing on it is not. Since Viper is a statically typed +language, we see an arrow following the definition of the ``stock_available()`` +method, which simply represents the datatype which the function is expected +to return. In the method, we simply key into ``self.holdings`` with the +company's address and check it's holdings. + +Now, lets take a look at a method that lets a person buy stock from the +company's holding. + +.. literalinclude:: ../examples/stock/company.v.py + :language: python + :lines: 27-39 + +The ``buy_stock()`` method is a ``@payable`` method which takes an amount of +ether sent and calculates the ``buy_order`` (the stock value equivalence at +the time of call). The number of shares is deducted from the company's holdings +and transferred to the sender's in the ``holdings`` mapping. + +Now that people can buy shares, how do we check someone's holdings? + +.. literalinclude:: ../examples/stock/company.v.py + :language: python + :lines: 41-44 + +The ``get_holdings()`` is another ``@constant`` method that takes an ``address`` +and returns its corresponding stock holdings by keying into ``self.holdings``. + +.. literalinclude:: ../examples/stock/company.v.py + :language: python + :lines: 46-49 + +To check the ether balance of the company, we can simply call the getter method +``cash()``. + +.. literalinclude:: ../examples/stock/company.v.py + :language: python + :lines: 51-63 + +To sell a stock, we have the ``sell_stock()`` method which takes a number of +stocks a person wishes to sell, and sends the equivalent value in ether to the +seller's address. We first ``assert`` that the number of stocks the person +wishes to sell is a value greater than ``0``. We also ``assert`` to see that +the user can only sell as much as the user owns and that the company has enough +ether to complete the sale. If all conditions are met, the holdings are deducted +from the seller and given to the company. The ethers are then sent to the seller. + +.. literalinclude:: ../examples/stock/company.v.py + :language: python + :lines: 65-74 + +A stockholder can also transfer their stock to another stockholder with the +``transfer_stock()`` method. The method takes a receiver address and the number +of shares to send. It first ``asserts`` that the amount being sent is greater +than ``0`` and ``asserts`` whether the sender has enough stocks to send. If +both conditions are satisfied, the transfer is made. + +.. literalinclude:: ../examples/stock/company.v.py + :language: python + :lines: 76-84 + +The company is also allowed to pay out an amount in ether to an address by +calling the ``pay_bill()`` method. This method should only be callable by the +company and thus first checks whether the method caller's address matches that +of the company. Another important condition to check is that the company has +enough funds to pay the amount. If both conditions satisfy, the contract +sends its ether to an address. + +.. literalinclude:: ../examples/stock/company.v.py + :language: python + :lines: 86-89 + +We can also check how much the company has raised by multiplying the number of +shares the company has sold and the price of each share. We can get this value +by calling the ``debt()`` method. + +.. literalinclude:: ../examples/stock/company.v.py + :language: python + :lines: 91-94 + +Finally, in this ``worth()`` method, we can check the worth of a company by +subtracting its debt from its ether balance. + +This contract has been the most thorough example so far in terms of its +functionality and features. Yet despite the thoroughness of such a contract, the +logic remained simple. Hopefully, by now, the Viper language has convinced you +of its capabilities and readability in writing smart contracts. diff --git a/setup.cfg b/setup.cfg index 7d886094c5..16d1a83503 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,12 +15,11 @@ python_files = test_*.py testpaths = tests [flake8] -exclude = tests -ignore = +exclude = tests, docs +ignore = E122 E124 E127 E128 E501 E731 - From 484da3b2a482d3b730484e386a75575feb52b153 Mon Sep 17 00:00:00 2001 From: dustin Date: Thu, 26 Oct 2017 03:49:12 +0200 Subject: [PATCH 004/162] fixes voting with delegation example --- examples/voting/ballot.v.py | 94 ++++++++++++++++------- tests/examples/voting/test_ballot.py | 109 +++++++++++++++++++++++++-- 2 files changed, 167 insertions(+), 36 deletions(-) diff --git a/examples/voting/ballot.v.py b/examples/voting/ballot.v.py index 519271edae..ba7ad07af2 100644 --- a/examples/voting/ballot.v.py +++ b/examples/voting/ballot.v.py @@ -4,11 +4,11 @@ voters: public({ # weight is accumulated by delegation weight: num, - # if true, that person already voted + # if true, that person already voted (includes voting by delegating) voted: bool, # person delegated to delegate: address, - # index of the voted proposal + # index of the voted proposal. not meaningful unless `voted` is True. vote: num }[address]) @@ -22,6 +22,15 @@ voter_count: public(num) chairperson: public(address) +num_proposals: public(num) + +@constant +def delegated(addr: address) -> bool: + return self.voters[addr].delegate != 0x0000000000000000000000000000000000000000 + +@constant +def directly_voted(addr: address) -> bool: + return self.voters[addr].voted and self.voters[addr].delegate == 0x0000000000000000000000000000000000000000 # Setup global variables def __init__(_proposalNames: bytes32[2]): @@ -32,61 +41,88 @@ def __init__(_proposalNames: bytes32[2]): name: _proposalNames[i], vote_count: 0 } + self.num_proposals += 1 # Give `voter` the right to vote on this ballot. # May only be called by `chairperson`. def give_right_to_vote(voter: address): - # Throws if sender is not chairpers + # Throws if sender is not chairperson assert msg.sender == self.chairperson # Throws if voter has already voted assert not self.voters[voter].voted - # Throws if voters voting weight isn't 0 + # Throws if voter's voting weight isn't 0 assert self.voters[voter].weight == 0 self.voters[voter].weight = 1 self.voter_count += 1 +# Used by `delegate`. Can be called by anyone. +def forward_weight(delegate_with_weight_to_forward: address): + assert self.delegated(delegate_with_weight_to_forward) + # Throw if there is nothing to do: + assert self.voters[delegate_with_weight_to_forward].weight > 0 + + target = self.voters[delegate_with_weight_to_forward].delegate + for i in range(4): + if self.delegated(target): + target = self.voters[target].delegate + # The following effectively detects cycles of length <= 5. + # BUT cycles aren't actually problematic for correctness; + # they just result in spoiled votes. + # So, in the production version, this should instead be + # the responsibility of the contract's client, and this + # check should be removed. + assert target != delegate_with_weight_to_forward + else: + # weight will be moved to someone who directly voted or + # hasn't voted + break + + weight_to_forward = self.voters[delegate_with_weight_to_forward].weight + self.voters[delegate_with_weight_to_forward].weight = 0 + self.voters[target].weight += weight_to_forward + + if self.directly_voted(target): + self.proposals[self.voters[target].vote].vote_count += weight_to_forward + self.voters[target].weight = 0 + + # To reiterate: if target is also a delegate, this function will need + # to be called again. + # Delegate your vote to the voter `to`. -def delegate(_to: address): - to = _to +def delegate(to: address): # Throws if sender has already voted - assert not self.voters[msg.sender].voted - # Throws if sender tries to delegate their vote to themselves - assert not msg.sender == to - # loop can delegate votes up to the current voter count - for i in range(self.voter_count, self.voter_count+1): - if self.voters[to].delegate: - # Because there are not while loops, use recursion to forward the delegation - # self.delegate(self.voters[to].delegate) - assert self.voters[to].delegate != msg.sender - to = self.voters[to].delegate + assert not self.voters[msg.sender].voted + # Throws if sender tries to delegate their vote to themselves or to + # the default address value of 0x0000000000000000000000000000000000000000 + # (the latter might not be problematic, but I don't want to think about it) + assert to != msg.sender and to != 0x0000000000000000000000000000000000000000 + self.voters[msg.sender].voted = True self.voters[msg.sender].delegate = to - if self.voters[to].voted: - # If the delegate already voted, - # directly add to the number of votes - self.proposals[self.voters[to].vote].vote_count += self.voters[msg.sender].weight - else: - # If the delegate did not vote yet, - # add to her weight. - self.voters[to].weight += self.voters[msg.sender].weight + + # this call will throw iff this delegation would cause a loop of length <= 5. + self.forward_weight(msg.sender) # Give your vote (including votes delegated to you) # to proposal `proposals[proposal].name`. def vote(proposal: num): - assert not self.voters[msg.sender].voted + # can't vote twice + assert not self.voters[msg.sender].voted + # can only vote on legitimate proposals + assert proposal < self.num_proposals self.voters[msg.sender].voted = True self.voters[msg.sender].vote = proposal - # If `proposal` is out of the range of the array, - # this will throw automatically and revert all - # changes. + + # transfer msg.sender's weight to proposal self.proposals[proposal].vote_count += self.voters[msg.sender].weight + self.voters[msg.sender].weight = 0 # Computes the winning proposal taking all # previous votes into account. @constant def winning_proposal() -> num: winning_vote_count = 0 - for i in range(5): + for i in range(2): if self.proposals[i].vote_count > winning_vote_count: winning_vote_count = self.proposals[i].vote_count winning_proposal = i diff --git a/tests/examples/voting/test_ballot.py b/tests/examples/voting/test_ballot.py index ccaa3f62b5..e0626edae5 100644 --- a/tests/examples/voting/test_ballot.py +++ b/tests/examples/voting/test_ballot.py @@ -15,6 +15,7 @@ def setUp(self): # Initialize tester, contract and expose relevant objects self.t = tester self.s = self.t.Chain() + self.s.head_state.gas_limit = 10**7 from viper import compiler self.t.languages['viper'] = compiler.Compiler() contract_code = open('examples/voting/ballot.v.py').read() @@ -63,26 +64,117 @@ def test_give_the_right_to_vote(self): # Check voters weight didn't change self.assertEqual(self.c.get_voters__weight(self.t.a5), 1) + def test_forward_weight(self): + self.c.give_right_to_vote(self.t.a0) + self.c.give_right_to_vote(self.t.a1) + self.c.give_right_to_vote(self.t.a2) + self.c.give_right_to_vote(self.t.a3) + self.c.give_right_to_vote(self.t.a4) + self.c.give_right_to_vote(self.t.a5) + self.c.give_right_to_vote(self.t.a6) + self.c.give_right_to_vote(self.t.a7) + self.c.give_right_to_vote(self.t.a8) + self.c.give_right_to_vote(self.t.a9) + + # aN(V) in these comments means address aN has vote weight V + + self.c.delegate(self.t.a2, sender=self.t.k1) + # a1(0) -> a2(2) a3(1) + self.c.delegate(self.t.a3, sender=self.t.k2) + # a1(0) -> a2(0) -> a3(3) + self.assertEqual(self.c.get_voters__weight(self.t.a1), 0) + self.assertEqual(self.c.get_voters__weight(self.t.a2), 0) + self.assertEqual(self.c.get_voters__weight(self.t.a3), 3) + + self.c.delegate(self.t.a9, sender=self.t.k8) + # a7(1) a8(0) -> a9(2) + self.c.delegate(self.t.a8, sender=self.t.k7) + # a7(0) -> a8(0) -> a9(3) + self.assertEqual(self.c.get_voters__weight(self.t.a7), 0) + self.assertEqual(self.c.get_voters__weight(self.t.a8), 0) + self.assertEqual(self.c.get_voters__weight(self.t.a9), 3) + self.c.delegate(self.t.a7, sender=self.t.k6) + self.c.delegate(self.t.a6, sender=self.t.k5) + self.c.delegate(self.t.a5, sender=self.t.k4) + # a4(0) -> a5(0) -> a6(0) -> a7(0) -> a8(0) -> a9(6) + self.assertEqual(self.c.get_voters__weight(self.t.a9), 6) + self.assertEqual(self.c.get_voters__weight(self.t.a8), 0) + + # a3(3) a4(0) -> a5(0) -> a6(0) -> a7(0) -> a8(0) -> a9(6) + self.c.delegate(self.t.a4, sender=self.t.k3) + # a3(0) -> a4(0) -> a5(0) -> a6(0) -> a7(0) -> a8(3) -> a9(6) + # a3's vote weight of 3 only makes it to a8 in the delegation chain: + self.assertEqual(self.c.get_voters__weight(self.t.a8), 3) + self.assertEqual(self.c.get_voters__weight(self.t.a9), 6) + + # call forward_weight again to move the vote weight the + # rest of the way: + self.c.forward_weight(self.t.a8) + # a3(0) -> a4(0) -> a5(0) -> a6(0) -> a7(0) -> a8(0) -> a9(9) + self.assertEqual(self.c.get_voters__weight(self.t.a8), 0) + self.assertEqual(self.c.get_voters__weight(self.t.a9), 9) + + # a0(1) -> a1(0) -> a2(0) -> a3(0) -> a4(0) -> a5(0) -> a6(0) -> a7(0) -> a8(0) -> a9(9) + self.c.delegate(self.t.a1, sender=self.t.k0) + # a0's vote weight of 1 only makes it to a5 in the delegation chain: + # a0(0) -> a1(0) -> a2(0) -> a3(0) -> a4(0) -> a5(1) -> a6(0) -> a7(0) -> a8(0) -> a9(9) + self.assertEqual(self.c.get_voters__weight(self.t.a5), 1) + self.assertEqual(self.c.get_voters__weight(self.t.a9), 9) + + # once again call forward_weight to move the vote weight the + # rest of the way: + self.c.forward_weight(self.t.a5) + # a0(0) -> a1(0) -> a2(0) -> a3(0) -> a4(0) -> a5(0) -> a6(0) -> a7(0) -> a8(0) -> a9(10) + self.assertEqual(self.c.get_voters__weight(self.t.a5), 0) + self.assertEqual(self.c.get_voters__weight(self.t.a9), 10) + + def test_block_short_cycle(self): + self.c.give_right_to_vote(self.t.a0) + self.c.give_right_to_vote(self.t.a1) + self.c.give_right_to_vote(self.t.a2) + self.c.give_right_to_vote(self.t.a3) + self.c.give_right_to_vote(self.t.a4) + self.c.give_right_to_vote(self.t.a5) + + self.c.delegate(self.t.a1, sender=self.t.k0) + self.c.delegate(self.t.a2, sender=self.t.k1) + self.c.delegate(self.t.a3, sender=self.t.k2) + self.c.delegate(self.t.a4, sender=self.t.k3) + # would create a length 5 cycle: + assert_tx_failed(self, lambda: self.c.delegate(self.t.a0, sender=self.t.k4)) + + self.c.delegate(self.t.a5, sender=self.t.k4) + # can't detect length 6 cycle, so this works: + self.c.delegate(self.t.a0, sender=self.t.k5) + # which is fine for the contract; those votes are simply spoiled. + # but this is something the frontend should prevent for user friendliness + def test_delegate(self): self.c.give_right_to_vote(self.t.a0) self.c.give_right_to_vote(self.t.a1) self.c.give_right_to_vote(self.t.a2) self.c.give_right_to_vote(self.t.a3) - # Voter can delegate - self.c.delegate(self.t.a0, sender=self.t.k1) - # Voters weight is 1 + # Voter's weight is 1 self.assertEqual(self.c.get_voters__weight(self.t.a1), 1) + # Voter can delegate: a1 -> a0 + self.c.delegate(self.t.a0, sender=self.t.k1) + # Voter's weight is now 0 + self.assertEqual(self.c.get_voters__weight(self.t.a1), 0) # Voter has voted self.assertEqual(self.c.get_voters__voted(self.t.a1), True) - # Delegates weight is 2 + # Delegate's weight is 2 self.assertEqual(self.c.get_voters__weight(self.t.a0), 2) # Voter cannot delegate twice assert_tx_failed(self, lambda: self.c.delegate(self.t.a2, sender=self.t.k1)) # Voter cannot delegate to themselves assert_tx_failed(self, lambda: self.c.delegate(self.t.a2, sender=self.t.k2)) - # Voter can delegatation is passed up to final delegate + # Voter CAN delegate to someone who hasn't been granted right to vote + # Exercise: prevent that + self.c.delegate(self.t.a6, sender=self.t.k2) + # Voter's delegatation is passed up to final delegate, yielding: + # a3 -> a1 -> a0 self.c.delegate(self.t.a1, sender=self.t.k3) - # Delegates weight is 3 + # Delegate's weight is 3 self.assertEqual(self.c.get_voters__weight(self.t.a0), 3) def test_vote(self): @@ -93,6 +185,7 @@ def test_vote(self): self.c.give_right_to_vote(self.t.a4) self.c.give_right_to_vote(self.t.a5) self.c.give_right_to_vote(self.t.a6) + self.c.give_right_to_vote(self.t.a7) self.c.delegate(self.t.a0, sender=self.t.k1) self.c.delegate(self.t.a1, sender=self.t.k3) # Voter can vote @@ -103,12 +196,14 @@ def test_vote(self): assert_tx_failed(self, lambda: self.c.vote(0)) # Voter cannot vote if they've delegated assert_tx_failed(self, lambda: self.c.vote(0, sender=self.t.k1)) - # Several voters can voter + # Several voters can vote self.c.vote(1, sender=self.t.k4) self.c.vote(1, sender=self.t.k2) self.c.vote(1, sender=self.t.k5) self.c.vote(1, sender=self.t.k6) self.assertEqual(self.c.get_proposals__vote_count(1), 4) + # Can't vote on a non-proposal + assert_tx_failed(self, lambda: self.c.vote(2, sender=self.t.k7)) def test_winning_proposal(self): self.c.give_right_to_vote(self.t.a0) From 21cc53ad2d8e7471a25cc13bd4f39f811329b57f Mon Sep 17 00:00:00 2001 From: dustin Date: Thu, 26 Oct 2017 12:48:58 +0200 Subject: [PATCH 005/162] use falsey-ness of 0x0000000000000000000000000000000000000000 --- tests/examples/voting/test_ballot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/examples/voting/test_ballot.py b/tests/examples/voting/test_ballot.py index e0626edae5..7439792d25 100644 --- a/tests/examples/voting/test_ballot.py +++ b/tests/examples/voting/test_ballot.py @@ -176,6 +176,7 @@ def test_delegate(self): self.c.delegate(self.t.a1, sender=self.t.k3) # Delegate's weight is 3 self.assertEqual(self.c.get_voters__weight(self.t.a0), 3) + self.assertEqual(0x0000000000000000000000000000000000000000, 0x0) def test_vote(self): self.c.give_right_to_vote(self.t.a0) From 205c6222f4162a5bf1bec0814df9db7d7fa82b33 Mon Sep 17 00:00:00 2001 From: dustin Date: Thu, 26 Oct 2017 12:51:47 +0200 Subject: [PATCH 006/162] use falsey-ness of 0x0000000000000000000000000000000000000000 (reverted from commit 21cc53ad2d8e7471a25cc13bd4f39f811329b57f) --- tests/examples/voting/test_ballot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/examples/voting/test_ballot.py b/tests/examples/voting/test_ballot.py index 7439792d25..e0626edae5 100644 --- a/tests/examples/voting/test_ballot.py +++ b/tests/examples/voting/test_ballot.py @@ -176,7 +176,6 @@ def test_delegate(self): self.c.delegate(self.t.a1, sender=self.t.k3) # Delegate's weight is 3 self.assertEqual(self.c.get_voters__weight(self.t.a0), 3) - self.assertEqual(0x0000000000000000000000000000000000000000, 0x0) def test_vote(self): self.c.give_right_to_vote(self.t.a0) From 7bc3e9a4162375badb27affcfc9776345a567786 Mon Sep 17 00:00:00 2001 From: dustin Date: Thu, 26 Oct 2017 12:55:05 +0200 Subject: [PATCH 007/162] use falsey-ness of 0x0000000000000000000000000000000000000000 --- examples/voting/ballot.v.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/voting/ballot.v.py b/examples/voting/ballot.v.py index ba7ad07af2..5de4f25e52 100644 --- a/examples/voting/ballot.v.py +++ b/examples/voting/ballot.v.py @@ -26,11 +26,13 @@ @constant def delegated(addr: address) -> bool: - return self.voters[addr].delegate != 0x0000000000000000000000000000000000000000 + # equivalent to self.voters[addr].delegate != 0x0000000000000000000000000000000000000000 + return not not self.voters[addr].delegate @constant def directly_voted(addr: address) -> bool: - return self.voters[addr].voted and self.voters[addr].delegate == 0x0000000000000000000000000000000000000000 + # not
equivalent to
== 0x0000000000000000000000000000000000000000 + return self.voters[addr].voted and not self.voters[addr].delegate # Setup global variables def __init__(_proposalNames: bytes32[2]): @@ -95,7 +97,7 @@ def delegate(to: address): # Throws if sender tries to delegate their vote to themselves or to # the default address value of 0x0000000000000000000000000000000000000000 # (the latter might not be problematic, but I don't want to think about it) - assert to != msg.sender and to != 0x0000000000000000000000000000000000000000 + assert to != msg.sender and not not to self.voters[msg.sender].voted = True self.voters[msg.sender].delegate = to From d904b9de12282fe65a57cf005e17f8269e05e9bf Mon Sep 17 00:00:00 2001 From: dustin Date: Thu, 26 Oct 2017 17:05:04 +0200 Subject: [PATCH 008/162] iff --> if and only if --- examples/voting/ballot.v.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/voting/ballot.v.py b/examples/voting/ballot.v.py index 5de4f25e52..964650c251 100644 --- a/examples/voting/ballot.v.py +++ b/examples/voting/ballot.v.py @@ -102,7 +102,7 @@ def delegate(to: address): self.voters[msg.sender].voted = True self.voters[msg.sender].delegate = to - # this call will throw iff this delegation would cause a loop of length <= 5. + # this call will throw if and only if this delegation would cause a loop of length <= 5. self.forward_weight(msg.sender) # Give your vote (including votes delegated to you) From e578258fb928c1dc9f89c9d8b34ef8ded79073bb Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Mon, 30 Oct 2017 15:36:17 +0200 Subject: [PATCH 009/162] Adds basic for loop for in memory list. --- .../features/iteration/test_for_in_list.py | 18 ++++++++++ viper/parser/stmt.py | 33 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tests/parser/features/iteration/test_for_in_list.py diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py new file mode 100644 index 0000000000..019ce92f8c --- /dev/null +++ b/tests/parser/features/iteration/test_for_in_list.py @@ -0,0 +1,18 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation, get_contract + + +def test_basic_in_list(): + code = """ +def data() -> num: + s = [1, 2, 3, 4, 5, 6] + for i in s: + if i >= 3: + return i + return -1 + """ + + c = get_contract(code) + + assert c.data() == 3 diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index e1b6ed6be3..02635c0ab8 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -152,12 +152,19 @@ def parse_for(self): from .parser import ( parse_body, ) + + # Type 0 for, eg. for i in list(): ... + iter_var_type = self.context.vars.get(self.stmt.iter.id).typ if isinstance(self.stmt.iter, ast.Name) else None + if isinstance(self.stmt.iter, ast.List) or isinstance(iter_var_type, ListType): + return self.parse_for_list() + if not isinstance(self.stmt.iter, ast.Call) or \ not isinstance(self.stmt.iter.func, ast.Name) or \ not isinstance(self.stmt.target, ast.Name) or \ self.stmt.iter.func.id != "range" or \ len(self.stmt.iter.args) not in (1, 2): raise StructureException("For statements must be of the form `for i in range(rounds): ..` or `for i in range(start, start + rounds): ..`", self.stmt.iter) + # Type 1 for, eg. for i in range(10): ... if len(self.stmt.iter.args) == 1: if not isinstance(self.stmt.iter.args[0], ast.Num): @@ -185,6 +192,32 @@ def parse_for(self): self.context.forvars[varname] = True return o + def parse_for_list(self): + from .parser import ( + parse_body, + ) + + iter_var_type = self.context.vars.get(self.stmt.iter.id).typ if isinstance(self.stmt.iter, ast.Name) else None + if iter_var_type and not isinstance(iter_var_type.subtype, BaseType): + raise StructureException('For loops allowed only on basetype lists.', self.stmt.iter) + + if iter_var_type: # We have a list that is already allocated. + iter_var = self.context.vars.get(self.stmt.iter.id) + varname = self.stmt.target.id + i_pos = self.context.new_variable('_i', BaseType('num')) + value_pos = self.context.vars[varname].pos if varname in self.context.forvars else self.context.new_variable(varname, BaseType('num')) + body = [ + 'seq', + ['mstore', value_pos, ['mload', ['add', iter_var.pos, ['mul', ['mload', i_pos], 32]]]], + parse_body(self.stmt.body, self.context) + ] + o = LLLnode.from_list( + ['repeat', i_pos, 0, iter_var.size, body], typ=None, pos=getpos(self.stmt) + ) + return o + else: + raise StructureException('For loop with list type not supported', self.stmt.iter) + def aug_assign(self): target = Expr.parse_variable_location(self.stmt.target, self.context) sub = Expr.parse_value_expr(self.stmt.value, self.context) From 706ec2efd710056b38c2f6d753f57469d082962c Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Mon, 30 Oct 2017 22:26:05 +0200 Subject: [PATCH 010/162] Adds test for literal list. --- .../parser/features/iteration/test_for_in_list.py | 15 +++++++++++++++ viper/parser/stmt.py | 1 + 2 files changed, 16 insertions(+) diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py index 019ce92f8c..ed327669ae 100644 --- a/tests/parser/features/iteration/test_for_in_list.py +++ b/tests/parser/features/iteration/test_for_in_list.py @@ -1,6 +1,7 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ get_contract_with_gas_estimation, get_contract +from viper.exceptions import TypeMismatchException def test_basic_in_list(): @@ -16,3 +17,17 @@ def data() -> num: c = get_contract(code) assert c.data() == 3 + + +def test_basic_in_list(): + code = """ +def data() -> num: + for i in [3, 5, 7, 9]: + if i > 5: + return i + return -1 + """ + + c = get_contract(code) + + assert c.data() == 7 diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index 02635c0ab8..b921cf82c6 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -216,6 +216,7 @@ def parse_for_list(self): ) return o else: + import ipdb; ipdb.set_trace() raise StructureException('For loop with list type not supported', self.stmt.iter) def aug_assign(self): From f51d04d6610170da0a4b6fe53fded78951f5568c Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Tue, 31 Oct 2017 13:13:18 +0200 Subject: [PATCH 011/162] Adds for i in list support from storage and list literals. --- .../features/iteration/test_for_in_list.py | 25 ++++++- viper/parser/stmt.py | 69 ++++++++++++++++--- 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py index ed327669ae..425a77cd47 100644 --- a/tests/parser/features/iteration/test_for_in_list.py +++ b/tests/parser/features/iteration/test_for_in_list.py @@ -4,7 +4,7 @@ from viper.exceptions import TypeMismatchException -def test_basic_in_list(): +def test_basic_for_in_list(): code = """ def data() -> num: s = [1, 2, 3, 4, 5, 6] @@ -19,7 +19,7 @@ def data() -> num: assert c.data() == 3 -def test_basic_in_list(): +def test_basic_for_list_liter(): code = """ def data() -> num: for i in [3, 5, 7, 9]: @@ -31,3 +31,24 @@ def data() -> num: c = get_contract(code) assert c.data() == 7 + + +def test_basic_for_list_storage(): + code = """ +x: num[4] + +def set(): + self.x = [3, 5, 7, 9] + +def data() -> num: + for i in self.x: + if i > 5: + return i + return -1 + """ + + c = get_contract(code) + + assert c.data() == -1 + assert c.set() is None + assert c.data() == 7 diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index b921cf82c6..88ac0cffd7 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -154,8 +154,7 @@ def parse_for(self): ) # Type 0 for, eg. for i in list(): ... - iter_var_type = self.context.vars.get(self.stmt.iter.id).typ if isinstance(self.stmt.iter, ast.Name) else None - if isinstance(self.stmt.iter, ast.List) or isinstance(iter_var_type, ListType): + if self._is_list_iter(): return self.parse_for_list() if not isinstance(self.stmt.iter, ast.Call) or \ @@ -163,7 +162,7 @@ def parse_for(self): not isinstance(self.stmt.target, ast.Name) or \ self.stmt.iter.func.id != "range" or \ len(self.stmt.iter.args) not in (1, 2): - raise StructureException("For statements must be of the form `for i in range(rounds): ..` or `for i in range(start, start + rounds): ..`", self.stmt.iter) + raise StructureException("For statements must be of the form `for i in range(rounds): ..` or `for i in range(start, start + rounds): ..`", self.stmt.iter) # noqa # Type 1 for, eg. for i in range(10): ... if len(self.stmt.iter.args) == 1: @@ -192,20 +191,39 @@ def parse_for(self): self.context.forvars[varname] = True return o + def _is_list_iter(self): + """ + Test if the current statement is a type of list, used in for loops. + """ + + # Check for literal or memory list. + iter_var_type = self.context.vars.get(self.stmt.iter.id).typ if isinstance(self.stmt.iter, ast.Name) else None + if isinstance(self.stmt.iter, ast.List) or isinstance(iter_var_type, ListType): + return True + + # Check for storage list. + if isinstance(self.stmt.iter, ast.Attribute): + iter_var_type = self.context.globals.get(self.stmt.iter.attr) + if iter_var_type and isinstance(iter_var_type.typ, ListType): + return True + + return False + def parse_for_list(self): from .parser import ( parse_body, + make_setter ) iter_var_type = self.context.vars.get(self.stmt.iter.id).typ if isinstance(self.stmt.iter, ast.Name) else None if iter_var_type and not isinstance(iter_var_type.subtype, BaseType): raise StructureException('For loops allowed only on basetype lists.', self.stmt.iter) - if iter_var_type: # We have a list that is already allocated. + varname = self.stmt.target.id + value_pos = self.context.vars[varname].pos if varname in self.context.forvars else self.context.new_variable(varname, BaseType('num')) + i_pos = self.context.new_variable('_i', BaseType('num')) + if iter_var_type: # Is a list that is already allocated to memory. iter_var = self.context.vars.get(self.stmt.iter.id) - varname = self.stmt.target.id - i_pos = self.context.new_variable('_i', BaseType('num')) - value_pos = self.context.vars[varname].pos if varname in self.context.forvars else self.context.new_variable(varname, BaseType('num')) body = [ 'seq', ['mstore', value_pos, ['mload', ['add', iter_var.pos, ['mul', ['mload', i_pos], 32]]]], @@ -215,9 +233,40 @@ def parse_for_list(self): ['repeat', i_pos, 0, iter_var.size, body], typ=None, pos=getpos(self.stmt) ) return o - else: - import ipdb; ipdb.set_trace() - raise StructureException('For loop with list type not supported', self.stmt.iter) + elif isinstance(self.stmt.iter, ast.List): # List gets defined in the for statement. + # Allocate list to memory. + list_literal = Expr(self.stmt.iter, self.context).lll_node + count = list_literal.typ.count + tmp_list = LLLnode.from_list( + obj=self.context.new_placeholder(ListType(list_literal.typ.subtype, count)), + typ=ListType(list_literal.typ.subtype, count), + location='memory' + ) + setter = make_setter(tmp_list, list_literal, 'memory') + body = [ + 'seq', + ['mstore', value_pos, ['mload', ['add', tmp_list, ['mul', ['mload', i_pos], 32]]]], + parse_body(self.stmt.body, self.context) + ] + o = LLLnode.from_list( + ['seq', + setter, + ['repeat', i_pos, 0, count, body]], typ=None, pos=getpos(self.stmt) + ) + return o + elif isinstance(self.stmt.iter, ast.Attribute): # List is contained in storage. + storage_list = Expr(self.stmt.iter, self.context).lll_node + count = storage_list.typ.count + body = [ + 'seq', + ['mstore', value_pos, ['sload', ['add', ['sha3_32', storage_list], ['mload', i_pos]]]], + parse_body(self.stmt.body, self.context), + ] + o = LLLnode.from_list( + ['seq', + ['repeat', i_pos, 0, count, body]], typ=None, pos=getpos(self.stmt) + ) + return o def aug_assign(self): target = Expr.parse_variable_location(self.stmt.target, self.context) From 47b44d3b6a6207b59bcc0b8d07e65ca2b51900d5 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Tue, 31 Oct 2017 15:02:16 +0200 Subject: [PATCH 012/162] Adds support for i in list of other BaseTypes. --- .../features/iteration/test_for_in_list.py | 78 ++++++++++++++++++- tests/parser/syntax/test_nested_list.py | 9 +++ viper/parser/stmt.py | 23 +++--- 3 files changed, 98 insertions(+), 12 deletions(-) diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py index 425a77cd47..366bac6c1c 100644 --- a/tests/parser/features/iteration/test_for_in_list.py +++ b/tests/parser/features/iteration/test_for_in_list.py @@ -1,7 +1,6 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ get_contract_with_gas_estimation, get_contract -from viper.exceptions import TypeMismatchException def test_basic_for_in_list(): @@ -52,3 +51,80 @@ def data() -> num: assert c.data() == -1 assert c.set() is None assert c.data() == 7 + + +def test_basic_for_list_address(): + code = """ +def data() -> address: + addresses = [ + 0x7d577a597B2742b498Cb5Cf0C26cDCD726d39E6e, + 0x82A978B3f5962A5b0957d9ee9eEf472EE55B42F1, + 0xDCEceAF3fc5C0a63d195d69b1A90011B7B19650D + ] + count = 0 + for i in addresses: + count += 1 + if count == 2: + return i + return 0x0000000000000000000000000000000000000000 + """ + + c = get_contract(code) + + assert c.data() == "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" + + +def test_basic_for_list_storage_address(): + code = """ +addresses: address[3] + +def set(i: num, val: address): + self.addresses[i] = val + +def ret(i: num) -> address: + return self.addresses[i] + +def iterate_return_second() -> address: + count = 0 + for i in self.addresses: + count += 1 + if count == 2: + return i + """ + + c = get_contract(code) + + c.set(0, '0x82A978B3f5962A5b0957d9ee9eEf472EE55B42F1') + c.set(1, '0x7d577a597B2742b498Cb5Cf0C26cDCD726d39E6e') + c.set(2, '0xDCEceAF3fc5C0a63d195d69b1A90011B7B19650D') + + assert c.ret(1) == c.iterate_return_second() == "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" + + +def test_basic_for_list_storage_decimal(): + code = """ +readings: decimal[3] + +def set(i: num, val: decimal): + self.readings[i] = val + +def ret(i: num) -> decimal: + return self.readings[i] + +def i_return(break_count: num) -> decimal: + count = 0 + for i in self.readings: + if count == break_count: + return i + count += 1 + """ + + c = get_contract(code) + + c.set(0, 0.0001) + c.set(1, 1.1) + c.set(2, 2.2) + + assert c.ret(2) == c.i_return(2) == 2.2 + assert c.ret(1) == c.i_return(1) == 1.1 + assert c.ret(0) == c.i_return(0) == 0.0001 diff --git a/tests/parser/syntax/test_nested_list.py b/tests/parser/syntax/test_nested_list.py index f05328525d..0449103783 100644 --- a/tests/parser/syntax/test_nested_list.py +++ b/tests/parser/syntax/test_nested_list.py @@ -29,6 +29,15 @@ def foo() -> num[2][2]: def foo(x: num[2][2]) -> num: self.y = x + """, + """ +bar: num[3][3] + +def foo() -> num[3]: + self.bar = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + for x in self.bar: + if x == [4, 5, 6]: + return x """ ] diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index 88ac0cffd7..ce3cf33bc3 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -216,12 +216,15 @@ def parse_for_list(self): ) iter_var_type = self.context.vars.get(self.stmt.iter.id).typ if isinstance(self.stmt.iter, ast.Name) else None - if iter_var_type and not isinstance(iter_var_type.subtype, BaseType): + if iter_var_type and not isinstance(iter_var_type.subtype, BaseType): # Sanity check on list subtype. raise StructureException('For loops allowed only on basetype lists.', self.stmt.iter) + iter_list_node = Expr(self.stmt.iter, self.context).lll_node + subtype = iter_list_node.typ.subtype.typ varname = self.stmt.target.id - value_pos = self.context.vars[varname].pos if varname in self.context.forvars else self.context.new_variable(varname, BaseType('num')) - i_pos = self.context.new_variable('_i', BaseType('num')) + value_pos = self.context.new_variable(varname, BaseType(subtype)) + i_pos = self.context.new_variable('_i', BaseType(subtype)) + if iter_var_type: # Is a list that is already allocated to memory. iter_var = self.context.vars.get(self.stmt.iter.id) body = [ @@ -235,14 +238,13 @@ def parse_for_list(self): return o elif isinstance(self.stmt.iter, ast.List): # List gets defined in the for statement. # Allocate list to memory. - list_literal = Expr(self.stmt.iter, self.context).lll_node - count = list_literal.typ.count + count = iter_list_node.typ.count tmp_list = LLLnode.from_list( - obj=self.context.new_placeholder(ListType(list_literal.typ.subtype, count)), - typ=ListType(list_literal.typ.subtype, count), + obj=self.context.new_placeholder(ListType(iter_list_node.typ.subtype, count)), + typ=ListType(iter_list_node.typ.subtype, count), location='memory' ) - setter = make_setter(tmp_list, list_literal, 'memory') + setter = make_setter(tmp_list, iter_list_node, 'memory') body = [ 'seq', ['mstore', value_pos, ['mload', ['add', tmp_list, ['mul', ['mload', i_pos], 32]]]], @@ -255,11 +257,10 @@ def parse_for_list(self): ) return o elif isinstance(self.stmt.iter, ast.Attribute): # List is contained in storage. - storage_list = Expr(self.stmt.iter, self.context).lll_node - count = storage_list.typ.count + count = iter_list_node.typ.count body = [ 'seq', - ['mstore', value_pos, ['sload', ['add', ['sha3_32', storage_list], ['mload', i_pos]]]], + ['mstore', value_pos, ['sload', ['add', ['sha3_32', iter_list_node], ['mload', i_pos]]]], parse_body(self.stmt.body, self.context), ] o = LLLnode.from_list( From e32f3cc42dcd819af48d824897aaad229c7fb4eb Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Tue, 31 Oct 2017 15:32:42 +0200 Subject: [PATCH 013/162] Add StructureException testcase for iterating over a nested list. --- tests/parser/syntax/test_nested_list.py | 14 +++++++++----- viper/parser/stmt.py | 7 +++---- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/parser/syntax/test_nested_list.py b/tests/parser/syntax/test_nested_list.py index 0449103783..23f8b77339 100644 --- a/tests/parser/syntax/test_nested_list.py +++ b/tests/parser/syntax/test_nested_list.py @@ -2,7 +2,7 @@ from pytest import raises from viper import compiler -from viper.exceptions import TypeMismatchException +from viper.exceptions import TypeMismatchException, StructureException fail_list = [ @@ -30,7 +30,7 @@ def foo() -> num[2][2]: def foo(x: num[2][2]) -> num: self.y = x """, - """ + (""" bar: num[3][3] def foo() -> num[3]: @@ -38,15 +38,19 @@ def foo() -> num[3]: for x in self.bar: if x == [4, 5, 6]: return x - """ + """, StructureException) ] @pytest.mark.parametrize('bad_code', fail_list) def test_nested_list_fail(bad_code): - with raises(TypeMismatchException): - compiler.compile(bad_code) + if isinstance(bad_code, tuple): + with raises(bad_code[1]): + compiler.compile(bad_code[0]) + else: + with raises(TypeMismatchException): + compiler.compile(bad_code) valid_list = [ diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index ce3cf33bc3..c50bc39d94 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -215,11 +215,10 @@ def parse_for_list(self): make_setter ) - iter_var_type = self.context.vars.get(self.stmt.iter.id).typ if isinstance(self.stmt.iter, ast.Name) else None - if iter_var_type and not isinstance(iter_var_type.subtype, BaseType): # Sanity check on list subtype. - raise StructureException('For loops allowed only on basetype lists.', self.stmt.iter) - iter_list_node = Expr(self.stmt.iter, self.context).lll_node + if not isinstance(iter_list_node.typ.subtype, BaseType): # Sanity check on list subtype. + raise StructureException('For loops allowed only on basetype lists.', self.stmt.iter) + iter_var_type = self.context.vars.get(self.stmt.iter.id).typ if isinstance(self.stmt.iter, ast.Name) else None subtype = iter_list_node.typ.subtype.typ varname = self.stmt.target.id value_pos = self.context.new_variable(varname, BaseType(subtype)) From 746bcd7f1299dc85ac03f55f6e90faf3ec379675 Mon Sep 17 00:00:00 2001 From: Bryant Eisenbach Date: Tue, 31 Oct 2017 16:09:03 -0600 Subject: [PATCH 014/162] Allow skip of the event name --- tests/setup_transaction_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/setup_transaction_tests.py b/tests/setup_transaction_tests.py index c7a50a602a..d7d6ff0fe3 100644 --- a/tests/setup_transaction_tests.py +++ b/tests/setup_transaction_tests.py @@ -118,7 +118,7 @@ def get_logs(receipt, contract, event_name=None): @pytest.fixture def get_log(): - def get_log(tester, contract, event_name): + def get_log(tester, contract, event_name=None): receipt = tester.s.head_state.receipts[-1] # Only the receipts for the last block # Get last log event with correct name and return the decoded event return get_logs(receipt, contract, event_name=event_name)[-1] From fbaea212d627edf6cfeb236022b160224c86ea46 Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 31 Oct 2017 17:14:20 -0500 Subject: [PATCH 015/162] Change function to func --- tests/setup_transaction_tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/setup_transaction_tests.py b/tests/setup_transaction_tests.py index c4b4a274d7..3040833df6 100644 --- a/tests/setup_transaction_tests.py +++ b/tests/setup_transaction_tests.py @@ -30,22 +30,22 @@ def inject_tx(txhex): chain.head_state.gas_limit = 10**9 -def check_gas(code, function=None, num_txs=1): - if function: - gas_estimate = tester.languages['viper'].gas_estimate(code)[function] +def check_gas(code, func=None, num_txs=1): + if func: + gas_estimate = tester.languages['viper'].gas_estimate(code)[func] else: gas_estimate = sum(tester.languages['viper'].gas_estimate(code).values()) gas_actual = chain.head_state.receipts[-1].gas_used \ - chain.head_state.receipts[-1-num_txs].gas_used \ - chain.last_tx.intrinsic_gas_used*num_txs - #Computed upper bound on the gas consumption should - #be greater than or equal to the amount of gas used + # Computed upper bound on the gas consumption should + # be greater than or equal to the amount of gas used if gas_estimate < gas_actual: raise Exception("Gas upper bound fail: bound %d actual %d" % (gas_estimate, gas_actual)) print('Function name: {} - Gas estimate {}, Actual: {}'.format( - function, gas_estimate, gas_actual) + func, gas_estimate, gas_actual) ) From 68725f4ab3217cda3461d9fd4133edfaeef86225 Mon Sep 17 00:00:00 2001 From: Bryant Date: Tue, 31 Oct 2017 16:18:51 -0600 Subject: [PATCH 016/162] Changed get_log() to get_last_log(), removed optional log name from usages --- tests/examples/tokens/test_vipercoin.py | 12 ++++++------ tests/setup_transaction_tests.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/examples/tokens/test_vipercoin.py b/tests/examples/tokens/test_vipercoin.py index a338c9f7ba..43c566eff7 100644 --- a/tests/examples/tokens/test_vipercoin.py +++ b/tests/examples/tokens/test_vipercoin.py @@ -4,7 +4,7 @@ from ethereum.abi import ValueOutOfBounds from ethereum.tools import tester -from tests.setup_transaction_tests import assert_tx_failed, get_log +from tests.setup_transaction_tests import assert_tx_failed, get_last_log TOKEN_NAME = "Vipercoin" @@ -113,7 +113,7 @@ def test_transferFrom(token_tester, assert_tx_failed): assert contract.allowance(a0, a1) == 0 -def test_transfer_event(token_tester, get_log): +def test_transfer_event(token_tester, get_last_log): a0 = token_tester.accounts[0] a1 = token_tester.accounts[1] k1 = token_tester.k1 @@ -123,7 +123,7 @@ def test_transfer_event(token_tester, get_log): assert contract.transfer(a1, 1) is True - assert get_log(tester, contract, 'Transfer') == { + assert get_last_log(tester, contract) == { '_from': '0x' + a0.hex(), '_to': '0x' + a1.hex(), '_value': 1, @@ -134,7 +134,7 @@ def test_transfer_event(token_tester, get_log): assert contract.approve(a1, 10) is True # approve 10 token transfers to a1. assert contract.transferFrom(a0, a2, 4, sender=k1) # transfer to a2, as a1, from a0's funds. - assert get_log(tester, contract, 'Transfer') == { + assert get_last_log(tester, contract) == { '_from': '0x' + a0.hex(), '_to': '0x' + a2.hex(), '_value': 4, @@ -142,7 +142,7 @@ def test_transfer_event(token_tester, get_log): } -def test_approval_event(token_tester, get_log): +def test_approval_event(token_tester, get_last_log): a0 = token_tester.accounts[0] a1 = token_tester.accounts[1] @@ -150,7 +150,7 @@ def test_approval_event(token_tester, get_log): assert contract.approve(a1, 10) is True # approve 10 token transfers to a1. - assert get_log(tester, contract, 'Approval') == { + assert get_last_log(tester, contract) == { '_owner': '0x' + a0.hex(), '_spender': '0x' + a1.hex(), '_value': 10, diff --git a/tests/setup_transaction_tests.py b/tests/setup_transaction_tests.py index d7d6ff0fe3..c5ebfd52c4 100644 --- a/tests/setup_transaction_tests.py +++ b/tests/setup_transaction_tests.py @@ -117,12 +117,12 @@ def get_logs(receipt, contract, event_name=None): return [contract.translator.decode_event(log.topics, log.data) for log in logs] @pytest.fixture -def get_log(): - def get_log(tester, contract, event_name=None): +def get_last_log(): + def get_last_log(tester, contract, event_name=None): receipt = tester.s.head_state.receipts[-1] # Only the receipts for the last block # Get last log event with correct name and return the decoded event return get_logs(receipt, contract, event_name=event_name)[-1] - return get_log + return get_last_log @pytest.fixture def assert_tx_failed(): From 452b4c86ecc49c246ffa9601f48d8689b0d4735d Mon Sep 17 00:00:00 2001 From: Edward He Date: Tue, 31 Oct 2017 20:18:31 -0400 Subject: [PATCH 017/162] Make corrections according to suggestions. --- docs/viper-by-example.rst | 42 +++++++++++---------------------------- 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/docs/viper-by-example.rst b/docs/viper-by-example.rst index 57a26653bd..9e7fd585c6 100644 --- a/docs/viper-by-example.rst +++ b/docs/viper-by-example.rst @@ -40,7 +40,7 @@ datatype ``address``. The ``beneficiary`` will be the receiver of money from the highest bidder. We also initialize the variables ``auction_start`` and ``auction_end`` with the datatype ``timestamp`` to manage the open auction period and ``highest_bid`` with datatype ``wei_value``, the smallest -denomination of ether, to manage auction state. The variable ``ended`` is a +denomination of ether, to manage auction state. Variable ``ended`` is a boolean to determine whether the auction is officially over. You may notice the all of the variables being passed into the ``public`` @@ -74,13 +74,13 @@ With initial setup out of the way, lets look at how our users can make bids. :language: python :lines: 24-38 -The ``@payable`` decorator will allow a user to send some ethers to the +The ``@payable`` decorator will allow a user to send some ether to the contract in order to call the decorated method. In this case, a user wanting to make a bid would call the ``bid()`` method while sending an amount equal to their desired bid (not including gas fees). When calling any method within a contract, we are provided with a built-in variable ``msg`` and we can access the public address of any method caller with ``msg.sender``. Similarly, the -amount of ether a user sends can be accessed by calling ``msg.sender``. +amount of ether a user sends can be accessed by calling ``msg.value``. .. warning:: ``msg.sender`` will change between internal function calls so that if you're calling a function from the outside, it's correct for the first @@ -89,12 +89,12 @@ amount of ether a user sends can be accessed by calling ``msg.sender``. Here, we first check whether the current time is before the auction's end time using the ``assert`` function which takes any boolean statement. We also check -to see if the new bid is greater than the highest bid. If both ``assert`` -statements pass, we continue to the next lines; otherwise, the ``bid()`` method -will throw an error and revert the transaction. If the two ``assert`` statements -the check that the previous bid is not equal to zero pass, we can safely -conclude that we have a valid new highest bid. We will send back the previous -``highest_bid`` to the previous ``highest_bidder`` and set our new +to see if the new bid is greater than the highest bid. If the two ``assert`` +statements pass, we can safely continue to the next lines; otherwise, the +``bid()`` method will throw an error and revert the transaction. If the two +``assert`` statements the check that the previous bid is not equal to zero pass, +we can safely conclude that we have a valid new highest bid. We will send back +the previous ``highest_bid`` to the previous ``highest_bidder`` and set our new ``highest_bid`` and ``highest_bidder``. .. literalinclude:: ../examples/auctions/simple_open_auction.v.py @@ -289,8 +289,7 @@ crowdfunding. We initialize the arguments as contract variables with their corresponding names. Additionally, a ``self.deadline`` is initialized to set a definitive end time for the crowdfunding period. -Now lets take a look at the method of how a person can participate in the -crowdfund. +Now lets take a look at how a person can participate in the crowdfund. .. literalinclude:: ../examples/crowdfund.v.py :language: python @@ -317,8 +316,8 @@ sends the collected funds to the beneficiary. .. note:: Notice that we have access to the total amount sent to the contract by calling ``self.balance``, a variable we never explicitly set. Similar to ``msg`` -and ``block``, ``self.balance`` is a variable thats comes free in all Viper -contracts. +and ``block``, ``self.balance`` is a built-in variable thats available in all +Viper contracts. We can finalize the campaign if all goes well, but what happens if the crowdfunding campaign isn't successful? We're going to need a way to refund @@ -337,23 +336,6 @@ in batches of 30 and refund them one at a time. Unfortunately, if there's a large number of of participants, multiple calls to ``refund()`` may be necessary. -One thing to note about the ``send()`` method is that it costs gas. Thus, -refunding each participant will deduct gas from the total balance of the -contract. This creates a big problem in that the last person to be refunded -bears the burden of paying the gas cost for all refunds. There are many ways -we can redesign this contract to be more fair to all participants in the case -of a refund. One potential way we can go about this is that the beneficiary or -the campaign owner set aside some initial funds during contract initialization to -bear the burden of the cost. But this also has the drawback of the not knowing -how much to set aside since the number of participants is unpredictable at the -time of contract initialization. - -As an exercise, can we think of another way to design this contract to shares -the refunding cost fairly among all participants? *Hint: Can we have the -participants come collect their fund individually instead of having the -contract batch refunding all participants?* - - .. index:: voting, ballot ****** From 2aeb9cf4f868597ee9d20bb2979864d9804da352 Mon Sep 17 00:00:00 2001 From: Edward He Date: Wed, 1 Nov 2017 00:48:30 -0400 Subject: [PATCH 018/162] Minor typo. --- docs/viper-by-example.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/viper-by-example.rst b/docs/viper-by-example.rst index 9e7fd585c6..a0ad6bf7be 100644 --- a/docs/viper-by-example.rst +++ b/docs/viper-by-example.rst @@ -40,10 +40,10 @@ datatype ``address``. The ``beneficiary`` will be the receiver of money from the highest bidder. We also initialize the variables ``auction_start`` and ``auction_end`` with the datatype ``timestamp`` to manage the open auction period and ``highest_bid`` with datatype ``wei_value``, the smallest -denomination of ether, to manage auction state. Variable ``ended`` is a +denomination of ether, to manage auction state. The variable ``ended`` is a boolean to determine whether the auction is officially over. -You may notice the all of the variables being passed into the ``public`` +You may notice all of the variables being passed into the ``public`` function. By declaring the variable *public*, the variable is callable by external contracts. Initializing the variables without the ``public`` function defaults to a private declaration and thus only accessible to methods From 7ddfa93d9fad222c7354002da24745d1b87f43a7 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Wed, 1 Nov 2017 17:36:27 +0200 Subject: [PATCH 019/162] Adds basic bytes as map keys support. --- tests/parser/features/test_bytes_map_keys.py | 23 ++++++++++++++++++++ viper/parser/parser_utils.py | 8 ++++++- viper/types.py | 6 ++--- 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 tests/parser/features/test_bytes_map_keys.py diff --git a/tests/parser/features/test_bytes_map_keys.py b/tests/parser/features/test_bytes_map_keys.py new file mode 100644 index 0000000000..563f7161a0 --- /dev/null +++ b/tests/parser/features/test_bytes_map_keys.py @@ -0,0 +1,23 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation, get_contract + + +def test_basic_bytes_keys(): + code = """ +mapped_bytes: num[bytes <= 5] + +def set(k: bytes <= 5, v: num): + self.one = k + self.mapped_bytes[k] = v + + +def get(k: bytes <= 5) -> num: + return self.mapped_bytes[k] + """ + + c = get_contract(code) + + c.set("test", 54321) + + assert c.get("test") == 54321 diff --git a/viper/parser/parser_utils.py b/viper/parser/parser_utils.py index f88ece3f1a..055f176884 100644 --- a/viper/parser/parser_utils.py +++ b/viper/parser/parser_utils.py @@ -15,7 +15,8 @@ from viper.types import ( is_base_type, are_units_compatible, - get_size_of_type + get_size_of_type, + ceil32 ) from viper.utils import MemoryPositions, DECIMAL_DIVISOR @@ -378,6 +379,11 @@ def add_variable_offset(parent, key): if isinstance(typ, ListType): subtype = typ.subtype sub = ['uclamplt', base_type_conversion(key, key.typ, BaseType('num')), typ.count] + elif isinstance(typ, MappingType) and isinstance(key.typ, ByteArrayType): + if not isinstance(typ.keytype, ByteArrayType) or (typ.keytype.maxlen != key.typ.maxlen): + raise TypeMismatchException('Mapping keys of bytes cannot be cast, use exact same bytes type of: %s', str(typ.keytype)) + subtype = typ.valuetype + sub = LLLnode.from_list(['sha3', key.args[0].value, ceil32(key.typ.maxlen)]) else: subtype = typ.valuetype sub = base_type_conversion(key, key.typ, typ.keytype) diff --git a/viper/types.py b/viper/types.py index 7f10012070..dadce0328a 100644 --- a/viper/types.py +++ b/viper/types.py @@ -95,7 +95,7 @@ def __repr__(self): # Data structure for a key-value mapping class MappingType(NodeType): def __init__(self, keytype, valuetype): - if not isinstance(keytype, BaseType): + if not isinstance(keytype, (BaseType, ByteArrayType)): raise Exception("Dictionary keys must be a base type") self.keytype = keytype self.valuetype = valuetype @@ -283,8 +283,8 @@ def parse_type(item, location): if location == 'memory': raise InvalidTypeException("No mappings allowed for in-memory types, only fixed-size arrays", item) keytype = parse_type(item.slice.value, None) - if not isinstance(keytype, BaseType): - raise InvalidTypeException("Mapping keys must be base types", item.slice.value) + if not isinstance(keytype, (BaseType, ByteArrayType)): + raise InvalidTypeException("Mapping keys must be base or bytes types", item.slice.value) return MappingType(keytype, parse_type(item.value, location)) # Dicts, used to represent mappings, eg. {uint: uint}. Key must be a base type elif isinstance(item, ast.Dict): From f2075289f8a3f0c896b11c422cc73c1467efc5f0 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Wed, 1 Nov 2017 20:04:23 +0200 Subject: [PATCH 020/162] Adds support for mapping literal bytes. --- tests/parser/features/test_bytes_map_keys.py | 20 +++++++++++++++++++- viper/parser/parser_utils.py | 11 +++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/tests/parser/features/test_bytes_map_keys.py b/tests/parser/features/test_bytes_map_keys.py index 563f7161a0..bc42ade0f8 100644 --- a/tests/parser/features/test_bytes_map_keys.py +++ b/tests/parser/features/test_bytes_map_keys.py @@ -8,7 +8,6 @@ def test_basic_bytes_keys(): mapped_bytes: num[bytes <= 5] def set(k: bytes <= 5, v: num): - self.one = k self.mapped_bytes[k] = v @@ -21,3 +20,22 @@ def get(k: bytes <= 5) -> num: c.set("test", 54321) assert c.get("test") == 54321 + + +def test_basic_bytes_literal_key(): + code = """ +mapped_bytes: num[bytes <= 5] + + +def set(v: num): + self.mapped_bytes["test"] = v + +def get(k: bytes <= 5) -> num: + return self.mapped_bytes[k] + """ + + c = get_contract(code) + + c.set(54321) + + assert c.get("test") == 54321 diff --git a/viper/parser/parser_utils.py b/viper/parser/parser_utils.py index 055f176884..6dacdf654b 100644 --- a/viper/parser/parser_utils.py +++ b/viper/parser/parser_utils.py @@ -380,10 +380,17 @@ def add_variable_offset(parent, key): subtype = typ.subtype sub = ['uclamplt', base_type_conversion(key, key.typ, BaseType('num')), typ.count] elif isinstance(typ, MappingType) and isinstance(key.typ, ByteArrayType): - if not isinstance(typ.keytype, ByteArrayType) or (typ.keytype.maxlen != key.typ.maxlen): + if not isinstance(typ.keytype, ByteArrayType) or (typ.keytype.maxlen < key.typ.maxlen): raise TypeMismatchException('Mapping keys of bytes cannot be cast, use exact same bytes type of: %s', str(typ.keytype)) subtype = typ.valuetype - sub = LLLnode.from_list(['sha3', key.args[0].value, ceil32(key.typ.maxlen)]) + if len(key.args[0].args) == 3: # handle bytes literal. + sub = LLLnode.from_list([ + 'seq', + key, + ['sha3', ['add', key.args[0].args[-1], 32], ceil32(key.typ.maxlen)] + ]) + else: + sub = LLLnode.from_list(['sha3', ['add', key.args[0].value, 32], ceil32(key.typ.maxlen)]) else: subtype = typ.valuetype sub = base_type_conversion(key, key.typ, typ.keytype) From d87953397c8da11c21de82954899c9005fa9754e Mon Sep 17 00:00:00 2001 From: kaikaifeng Date: Thu, 2 Nov 2017 16:42:02 +0800 Subject: [PATCH 021/162] fix the problems when install viper in Ubuntu16.04 --- docs/installing-viper.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installing-viper.rst b/docs/installing-viper.rst index d5ff4e94fc..4cb74030f8 100644 --- a/docs/installing-viper.rst +++ b/docs/installing-viper.rst @@ -37,7 +37,7 @@ Install Python 3.6 and some necessary packages: wget https://www.python.org/ftp/python/3.6.2/Python-3.6.2.tgz tar xfz Python-3.6.2.tgz cd Python-3.6.2/ - ./configure –prefix /usr/local/lib/python3.6 + ./configure --prefix /usr/local/lib/python3.6 sudo make sudo make install @@ -75,7 +75,7 @@ other development environment set-up. To create a new virtual environment for Viper run the following commands: :: - virtualenv -p /usr/local/lib/python3.6/bin/python --no-site-packages ~/viper-venv + virtualenv -p /usr/local/lib/python3.6/bin/python3 --no-site-packages ~/viper-venv source ~/viper-venv/bin/activate To find out more about virtual environments, check out: From a9adb07bca7fe6f0a5b68caaae41f5ee40150a14 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Thu, 2 Nov 2017 12:15:23 +0200 Subject: [PATCH 022/162] Adds test for long than 32 bytes keys. --- tests/parser/features/test_bytes_map_keys.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/parser/features/test_bytes_map_keys.py b/tests/parser/features/test_bytes_map_keys.py index bc42ade0f8..645534c55c 100644 --- a/tests/parser/features/test_bytes_map_keys.py +++ b/tests/parser/features/test_bytes_map_keys.py @@ -39,3 +39,22 @@ def get(k: bytes <= 5) -> num: c.set(54321) assert c.get("test") == 54321 + + +def test_basic_long_bytes_as_keys(): + code = """ +mapped_bytes: num[bytes <= 34] + +def set(k: bytes <= 34, v: num): + self.mapped_bytes[k] = v + + +def get(k: bytes <= 34) -> num: + return self.mapped_bytes[k] + """ + + c = get_contract(code) + + c.set("a" * 34, 6789) + + assert c.get("a" * 34) == 6789 From f46762ddfc15ed0edb94db9ba2765bae6aae4c4c Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Thu, 2 Nov 2017 13:44:37 +0200 Subject: [PATCH 023/162] Adds test for mismatched bytes size map assign. --- tests/parser/features/test_bytes_map_keys.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/parser/features/test_bytes_map_keys.py b/tests/parser/features/test_bytes_map_keys.py index 645534c55c..f4fd495f5c 100644 --- a/tests/parser/features/test_bytes_map_keys.py +++ b/tests/parser/features/test_bytes_map_keys.py @@ -1,6 +1,7 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ get_contract_with_gas_estimation, get_contract +from viper.exceptions import TypeMismatchException def test_basic_bytes_keys(): @@ -58,3 +59,15 @@ def get(k: bytes <= 34) -> num: c.set("a" * 34, 6789) assert c.get("a" * 34) == 6789 + + +def test_mismatched_byte_length(): + code = """ +mapped_bytes: num[bytes <= 34] + +def set(k: bytes <= 35, v: num): + self.mapped_bytes[k] = v + """ + + with pytest.raises(TypeMismatchException): + c = get_contract(code) From be71ed0ef9cbf79b333d12a0ebf5fa02d1911407 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Fri, 3 Nov 2017 14:19:54 +0200 Subject: [PATCH 024/162] Adds variable name to i_pos, so it doesn't conflict. --- viper/parser/stmt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index c50bc39d94..c72e25bd2b 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -222,7 +222,7 @@ def parse_for_list(self): subtype = iter_list_node.typ.subtype.typ varname = self.stmt.target.id value_pos = self.context.new_variable(varname, BaseType(subtype)) - i_pos = self.context.new_variable('_i', BaseType(subtype)) + i_pos = self.context.new_variable('_index_for_' + varname, BaseType(subtype)) if iter_var_type: # Is a list that is already allocated to memory. iter_var = self.context.vars.get(self.stmt.iter.id) From a729570693797866d74c532d1ccc72aee2492363 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Fri, 3 Nov 2017 18:10:00 +0200 Subject: [PATCH 025/162] Adds exceptions for altering lists whilst iterating them. --- .../features/iteration/test_for_in_list.py | 39 +++++++++++++++++++ viper/parser/parser.py | 8 ++++ viper/parser/stmt.py | 20 ++++++++++ 3 files changed, 67 insertions(+) diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py index 366bac6c1c..878ea7af42 100644 --- a/tests/parser/features/iteration/test_for_in_list.py +++ b/tests/parser/features/iteration/test_for_in_list.py @@ -1,6 +1,7 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ get_contract_with_gas_estimation, get_contract +from viper.exceptions import StructureException def test_basic_for_in_list(): @@ -128,3 +129,41 @@ def i_return(break_count: num) -> decimal: assert c.ret(2) == c.i_return(2) == 2.2 assert c.ret(1) == c.i_return(1) == 1.1 assert c.ret(0) == c.i_return(0) == 0.0001 + + +def test_altering_list_within_for_loop(): + code = """ +def data() -> num: + s = [1, 2, 3, 4, 5, 6] + count = 0 + for i in s: + s[count] = 1 # this should not be allowed. + if i >= 3: + return i + count += 1 + return -1 + """ + + with pytest.raises(StructureException): + get_contract(code) + + +def test_altering_list_within_for_loop_storage(): + code = """ +s: num[6] + +def set(): + self.s = [1, 2, 3, 4, 5, 6] + +def data() -> num: + count = 0 + for i in self.s: + self.s[count] = 1 # this should not be allowed. + if i >= 3: + return i + count += 1 + return -1 + """ + + with pytest.raises(StructureException): + get_contract(code) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 5f987fb241..197bed0f01 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -240,6 +240,14 @@ def __init__(self, vars=None, globals=None, sigs=None, forvars=None, return_type self.placeholder_count = 1 # Original code (for error pretty-printing purposes) self.origcode = origcode + # In Loop status. Wether body is currently evaluating within a for-loop or not. + self.in_for_loop = set() + + def set_in_for_loop(self, name_of_list): + self.in_for_loop.add(name_of_list) + + def remove_in_for_loop(self, name_of_list): + self.in_for_loop.remove(name_of_list) # Add a new variable def new_variable(self, name, typ): diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index c72e25bd2b..1930dbec6c 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -78,6 +78,22 @@ def assign(self): from .parser import ( make_setter, ) + + if isinstance(self.stmt.targets[0], ast.Subscript): # Check if we are doing assignment of an iteration loop. + raise_exception = False + if isinstance(self.stmt.targets[0].value, ast.Attribute): + list_name = "%s.%s" % (self.stmt.targets[0].value.value.id, self.stmt.targets[0].value.attr) + if list_name in self.context.in_for_loop: + raise_exception = True + + if isinstance(self.stmt.targets[0].value, ast.Name) and \ + self.stmt.targets[0].value.id in self.context.in_for_loop: + list_name = self.stmt.targets[0].value.id + raise_exception = True + + if raise_exception: + raise StructureException("Altering list '%s' which is being iterated!" % list_name, self.stmt) + # Assignment (eg. x[4] = y) if len(self.stmt.targets) != 1: raise StructureException("Assignment statement must have one target", self.stmt) @@ -225,6 +241,7 @@ def parse_for_list(self): i_pos = self.context.new_variable('_index_for_' + varname, BaseType(subtype)) if iter_var_type: # Is a list that is already allocated to memory. + self.context.set_in_for_loop(self.stmt.iter.id) # make sure list cannot be altered whilst iterating. iter_var = self.context.vars.get(self.stmt.iter.id) body = [ 'seq', @@ -234,6 +251,7 @@ def parse_for_list(self): o = LLLnode.from_list( ['repeat', i_pos, 0, iter_var.size, body], typ=None, pos=getpos(self.stmt) ) + self.context.remove_in_for_loop(self.stmt.iter.id) return o elif isinstance(self.stmt.iter, ast.List): # List gets defined in the for statement. # Allocate list to memory. @@ -257,6 +275,7 @@ def parse_for_list(self): return o elif isinstance(self.stmt.iter, ast.Attribute): # List is contained in storage. count = iter_list_node.typ.count + self.context.set_in_for_loop(iter_list_node.annotation) # make sure list cannot be altered whilst iterating. body = [ 'seq', ['mstore', value_pos, ['sload', ['add', ['sha3_32', iter_list_node], ['mload', i_pos]]]], @@ -266,6 +285,7 @@ def parse_for_list(self): ['seq', ['repeat', i_pos, 0, count, body]], typ=None, pos=getpos(self.stmt) ) + self.context.remove_in_for_loop(iter_list_node.annotation) return o def aug_assign(self): From e092ca0a20adcc4f9ed1694e299a76187b93e717 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Fri, 3 Nov 2017 18:28:40 +0200 Subject: [PATCH 026/162] Fixe for altering list in iteration exception check. --- viper/parser/stmt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index 1930dbec6c..635bcd54a8 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -79,7 +79,7 @@ def assign(self): make_setter, ) - if isinstance(self.stmt.targets[0], ast.Subscript): # Check if we are doing assignment of an iteration loop. + if isinstance(self.stmt.targets[0], ast.Subscript) and self.context.in_for_loop: # Check if we are doing assignment of an iteration loop. raise_exception = False if isinstance(self.stmt.targets[0].value, ast.Attribute): list_name = "%s.%s" % (self.stmt.targets[0].value.value.id, self.stmt.targets[0].value.attr) From ead7538cf0b789b55c6585d1efed1a52d2de2f16 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Mon, 6 Nov 2017 15:25:45 +0200 Subject: [PATCH 027/162] Adds tests for assert x in list --- .../features/iteration/test_range_in.py | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/parser/features/iteration/test_range_in.py b/tests/parser/features/iteration/test_range_in.py index b0a7b4e8a1..612f542861 100644 --- a/tests/parser/features/iteration/test_range_in.py +++ b/tests/parser/features/iteration/test_range_in.py @@ -1,6 +1,6 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract + get_contract_with_gas_estimation, get_contract, assert_tx_failed from viper.exceptions import TypeMismatchException @@ -72,3 +72,29 @@ def testin() -> bool: """ with pytest.raises(TypeMismatchException): c = get_contract(code) + + +def test_ownership(assert_tx_failed): + code = """ + +owners: address[10] + +def __init__(): + self.owners[0] = msg.sender + +def set_owner(i: num): + self.owners[i] = msg.sender + +def is_owner() -> bool: + assert msg.sender in self.owners + return True + """ + + c = get_contract(code) + + assert c.is_owner() + c.set_owner(0, sender=t.k1) + assert c.is_owner(sender=t.k1) + + with pytest.raises(t.TransactionFailed): + c.is_owner(sender=t.k2) From 430eaa01955cd7fe0979b286046b37fc44f1c6ea Mon Sep 17 00:00:00 2001 From: chgue Date: Mon, 6 Nov 2017 16:16:37 +0100 Subject: [PATCH 028/162] Added reference types. --- docs/types.rst | 213 ++++++++++++++++++++++++++++++------------------- 1 file changed, 133 insertions(+), 80 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 46a83f1a40..8dd13cb97f 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -14,6 +14,9 @@ to form complex types. In addition, types can interact with each other in expressions containing operators. + +.. index:: ! value + *********** Value Types *********** @@ -28,7 +31,7 @@ Boolean ======= **Keyword:** ``bool`` -A Boolean is a type to store a logical/truth value. +A boolean is a type to store a logical/truth value. Values ------ @@ -40,12 +43,12 @@ Operators ==================== =================== Operator Description ==================== =================== -``f(x) not g(y)`` Logical negation -``f(x) and g(y)`` Logical conjunction -``f(x) or g(y)`` Logical disjunction -``f(x) == g(y)`` Equality -``f(x) != g(y)`` Inequality -==================== =================== +``x not y`` Logical negation +``x and y`` Logical conjunction +``x or y`` Logical disjunction +``x == y`` Equality +``x != y`` Inequality +=================== =================== The operators ``or`` and ``and`` apply the common short-circuiting rules: :: @@ -84,7 +87,7 @@ Operator Description ========== ================ ``x`` and ``y`` must be of the type ``num``. -Arithmetic operators +Arithmetic Operators ^^^^^^^^^^^^^^^^^^^^ ============= ====================== @@ -102,11 +105,6 @@ Operator Description ============= ====================== ``x`` and ``y`` must be of the type ``num``. -Conversion ----------- -A ``num`` can be converted to a ``num256`` with the function ``as_num256(x)``, where ``x`` is of the type ``num``. -Conversly, a ``num256`` can be converted to a ``num`` with the function ``as_num128(x)``, where ``x`` is of the type ``num256``. - .. index:: ! unit, ! num256 Unsigned Integer (256 bit) ========================== @@ -139,7 +137,7 @@ Operator Description =================== ================ ``x`` and ``y`` must be of the type ``num256``. -Arithmetic operators +Arithmetic Operators ^^^^^^^^^^^^^^^^^^^^ ======================= ====================== @@ -158,7 +156,7 @@ Operator Description ======================= ====================== ``x`` and ``y`` must be of the type ``num256``. -Bitwise operators +Bitwise Operators ^^^^^^^^^^^^^^^^^ ===================== ============= @@ -176,11 +174,6 @@ Operator Description Positive ``_shift`` equals a left shift; negative ``_shift`` equals a right shift. Values shifted above/below the most/least significant bit get discarded. -Conversion ----------- -A ``num256`` can be converted to a ``num`` with the function ``as_num128(x)``, where ``x`` is of the type ``num256``. -Conversly, a ``num`` can be converted to a ``num256`` with the function ``as_num256(x)``, where ``x`` is of the type ``num``. - Decimals ======== **Keyword:** ``decimal`` @@ -209,7 +202,7 @@ Operator Description ========== ================ ``x`` and ``y`` must be of the type ``decimal``. -Arithmetic operators +Arithmetic Operators ^^^^^^^^^^^^^^^^^^^^ ============= ========================================== @@ -236,7 +229,7 @@ The address type holds an Ethereum address. Values ------ -An Address type can hold an Ethereum address which equates to 20 bytes/160 bits. Returns in hexadecimal notation with a leading ``0x``. +An address type can hold an Ethereum address which equates to 20 bytes/160 bits. Returns in hexadecimal notation with a leading ``0x``. .. _members-of-addresses: Members @@ -278,73 +271,106 @@ Keyword Unit Base type Description ``currency2_value`` 1 currency2 ``num`` An amount of currency2 =================== =========== ========= ==================================================================================== -Conversion ----------- -The unit of a unit type may be stripped with the function ``as_unitless_number(_unitType)``, where ``_unitType`` is a unit type. The returned value is then either a ``num`` -or a ``decimal``, depending on the base type. - -################# -TODO from here on -################# -Todo: bytes32 and reference types; revist conversion between num/num256/bytes32 - -.. index:: byte array, bytes32 - - -Fixed-size byte arrays ----------------------- - -``bytes32``: 32 bytes +.. index:: !bytes32 +32-bit-wide Byte Array +====================== +**Keyword:** ``bytes32`` +A 32-bit-wide byte array. Otherwise similiar to byte arrays. +**Example:** :: - # Declaration hash: bytes32 - # Assignment self.hash = _hash +Operators +--------- +==================================== ============================================================ +Keyword Description +==================================== ============================================================ +``len(x)`` Returns the length as an integer +``sha3(x)`` Returns the sha3 hash as bytes32 +``concat(x, ...)`` Concatenates multiple inputs +``slice(x, start=_start, len=_len)`` Returns a slice of ``_len`` starting at ``_start`` +==================================== ============================================================ +Where ``x`` is a byte array and ``_start`` as well as ``_len`` are integer values. + +.. index:: !bytes +Fixed-size Byte Arrays +====================== +**Keyword:** ``bytes`` + +A byte array with a fixed size. +The syntax being ``bytes <= maxLen``, where ``maxLen`` is an integer which denotes the maximum number of bits. + +.. index:: !string +Strings +------- +Fixed-size byte arrays can hold strings with equal or fewer characters than the maximum length of the byte array. -``bytes <= maxlen``: a byte array with the given maximum length - +**Example:** :: + exampleString = "Test String" - # Declaration - name: bytes <= 5 +Operators +--------- +==================================== ============================================================ +Keyword Description +==================================== ============================================================ +``len(x)`` Returns the length as an integer +``sha3(x)`` Returns the sha3 hash as bytes32 +``concat(x, ...)`` Concatenates multiple inputs +``slice(x, start=_start, len=_len)`` Returns a slice of ``_len`` starting at ``_start`` +==================================== ============================================================ +Where ``x`` is a byte array and ``_start`` as well as ``_len`` are integer values. - # Assignment - self.name = _name +.. index:: !reference -``type[length]``: finite list +*************** +Reference Types +*************** -:: +Reference types do not fit into 32 Bytes. Because of this, copying their value is not as feasible as +with value types. Therefore only the location, the reference, of the data is passed. - # Declaration - numbers: num[3] +.. index:: !arrays +Fixed-size Lists +================ - # Assignment - self.numbers[0] = _num1 +Fixed-size lists hold a finite number of elements which belong to a specified type. + +Syntax +------ +Lists can be declared with ``_name: _ValueType[_Integer]``. Multidimensional lists are also possible. +**Example:** +:: + #Defining a list + exampleList: num[3] + #Setting values + exampleList = [10, 11, 12] + exampleList[2] = 42 + #Returning a value + return exampleList[0] .. index:: !structs - Structs -------- +======= -Structs are custom defined types that can group several variables. They can be accessed via ``struct.argname``. +Structs are custom defined types that can group several variables. +Syntax +------ +Structs can be accessed via ``struct.argname``. +**Example:** :: - - # Information about voters - voters: public({ - # weight is accumulated by delegation - weight: num, - # if true, that person already voted - voted: bool, - # person delegated to - delegate: address, - # index of the voted proposal - vote: num - }) + #Defining a struct + exampleStruct: { + value1: num, + value2: decimal, + } + #Accessing a value + exampleStruct.value1 = 1 .. index:: !mapping @@ -352,22 +378,49 @@ Structs are custom defined types that can group several variables. They can be Mappings ======== -Mapping types are declared as ``_ValueType[_KeyType]``. -Here ``_KeyType`` can be almost any type except for mappings, a contract, or a struct. -``_ValueType`` can actually be any type, including mappings. - -Mappings can be seen as `hash tables `_ which are virtually initialized such that +Mappings in Viper can be seen as `hash tables `_ which are virtually initialized such that every possible key exists and is mapped to a value whose byte-representation is -all zeros: a type's :ref:`default value `. The similarity ends here, though: The key data is not actually stored -in a mapping, only its ``keccak256`` hash used to look up the value. - -Because of this, mappings do not have a length or a concept of a key or value being "set". - -Mappings are only allowed as state variables. +all zeros: a type's default value. The similarity ends here, though: The key data is not actually stored +in a mapping, only its ``keccak256`` hash used to look up the value. Because of this, mappings +do not have a length or a concept of a key or value being "set". -It is possible to mark mappings ``public`` and have Viper create a :ref:`getter `. +It is possible to mark mappings ``public`` and have Viper create a getter. The ``_KeyType`` will become a required parameter for the getter and it will return ``_ValueType``. +.. note:: + Mappings are only allowed as state variables. + +Syntax +------ + +Mapping types are declared as ``_ValueType[_KeyType]``. +Here ``_KeyType`` can be almost any type except for mappings, a contract, or a struct. +``_ValueType`` can actually be any type, including mappings. + +**Example:** +:: + #Defining a mapping + exampleMapping: decimal[num] + #Accessing a value + exampleMapping[0] = 10.1 + .. note:: Mappings can only be accessed, not iterated over. + +.. index:: !conversion + +********** +Conversion +********** +Following conversions are possible. + +=================== ===================================================================================================================== ============= +Keyword Input Output +=================== ===================================================================================================================== ============= +``as_num128(x)`` ``num256``, ``address``, ``bytes32`` ``num`` +``as_num256(x)`` ``num`` , ``address``, ``bytes32`` ``num256`` +``as_bytes32(x)`` ``num``, ``num256``, ``address`` ``bytes32`` +``bytes_to_num(x)`` ``bytes`` ``num`` +``as_wei_value(x)`` ``num`` , ``decimal``; `denomination `_ literal ``wei_value`` +=================== ===================================================================================================================== ============= From 738eabb8290fe7050255dff7fca88928e0927692 Mon Sep 17 00:00:00 2001 From: Leo Arias Date: Tue, 7 Nov 2017 16:36:08 +0000 Subject: [PATCH 029/162] Add the packaging metadata to build the viper snap --- snap/snapcraft.yaml | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 snap/snapcraft.yaml diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 0000000000..9e0c143ee7 --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,41 @@ +name: viper +version: git +summary: Experimental language for the Ethereum Virtual Machine +description: | + Viper is an experimental, contract-oriented, pythonic programming language that targets the Ethereum Virtual Machine (EVM) + +grade: devel # must be 'stable' to release into candidate/stable channels +confinement: devmode # use 'strict' once you have the right plugs and slots + +apps: + viper: + command: viper + +parts: + viper: + source: . + plugin: python + python-version: python3 + after: [python] + python: + source: https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tar.xz + plugin: autotools + configflags: + - --prefix=/usr + build-packages: + - gcc + - libreadline-dev + - libncursesw5-dev + - zlib1g-dev + - libbz2-dev + - liblzma-dev + - libgdbm-dev + - libdb-dev + - libssl-dev + - libexpat1-dev + - libmpdec-dev + - libbluetooth-dev + - libsqlite3-dev + - libffi-dev + stage: + - -usr/lib/python3.6/test From 15c5ea8d05d33756a64acfdbf98d09ebd0fd091a Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Tue, 7 Nov 2017 20:05:10 +0200 Subject: [PATCH 030/162] Improves ownership test - to be more realistic. --- .../features/iteration/test_range_in.py | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/tests/parser/features/iteration/test_range_in.py b/tests/parser/features/iteration/test_range_in.py index 612f542861..a5af619837 100644 --- a/tests/parser/features/iteration/test_range_in.py +++ b/tests/parser/features/iteration/test_range_in.py @@ -71,30 +71,36 @@ def testin() -> bool: return False """ with pytest.raises(TypeMismatchException): - c = get_contract(code) + get_contract(code) def test_ownership(assert_tx_failed): code = """ -owners: address[10] +owners: address[2] def __init__(): self.owners[0] = msg.sender -def set_owner(i: num): - self.owners[i] = msg.sender +def set_owner(i: num, new_owner: address): + assert msg.sender in self.owners + self.owners[i] = new_owner def is_owner() -> bool: - assert msg.sender in self.owners - return True + return msg.sender in self.owners """ c = get_contract(code) - assert c.is_owner() - c.set_owner(0, sender=t.k1) - assert c.is_owner(sender=t.k1) + assert c.is_owner() is True # contract creator is owner. + assert c.is_owner(sender=t.k1) is False # no one else is. + + # only an owner may set another owner. + assert_tx_failed(t, lambda: c.set_owner(1, t.a1, sender=t.k1)) + + c.set_owner(1, t.a1) + assert c.is_owner(sender=t.k1) is True - with pytest.raises(t.TransactionFailed): - c.is_owner(sender=t.k2) + # Owner in place 0 can be replaced. + c.set_owner(0, t.a1) + assert c.is_owner() is False From 7a92cd95db37608ca40be63ea3da7cc502b700d4 Mon Sep 17 00:00:00 2001 From: Bryant Date: Tue, 7 Nov 2017 17:14:29 -0500 Subject: [PATCH 031/162] Modified assert_tx_failed() to remove reference to tester as tester reference inside setup_transaction_tests.py was referencing the same module --- examples/stock/test_company.py | 32 +++++++++---------- .../auctions/test_simple_open_auction.py | 12 +++---- .../test_safe_remote_purchase.py | 16 +++++----- tests/examples/tokens/test_vipercoin.py | 2 -- tests/examples/wallet/test_wallet.py | 8 ++--- .../features/test_external_contract_calls.py | 20 ++++++------ tests/parser/features/test_logging.py | 26 +++++++-------- tests/parser/types/numbers/test_num256.py | 22 ++++++------- tests/setup_transaction_tests.py | 2 +- 9 files changed, 69 insertions(+), 71 deletions(-) diff --git a/examples/stock/test_company.py b/examples/stock/test_company.py index 17fbb5fc1d..2c0008a1dc 100644 --- a/examples/stock/test_company.py +++ b/examples/stock/test_company.py @@ -19,7 +19,7 @@ def tester(): return tester @pytest.fixture -def assert_tx_failed(tester, function_to_test, exception = t.TransactionFailed): +def assert_tx_failed(function_to_test, exception = t.TransactionFailed): initial_state = tester.s.snapshot() with pytest.raises(exception): function_to_test() @@ -34,15 +34,15 @@ def test_overbuy(tester): assert tester.c.stock_available() == 0 assert tester.c.get_holding(t.a1) == (test_shares * 2) one_stock = tester.c.get_price() - assert_tx_failed(tester, lambda: tester.c.buy_stock(sender=t.k1, value=one_stock)) - assert_tx_failed(tester, lambda: tester.c.buy_stock(sender=t.k2, value=one_stock)) + assert_tx_failed(lambda: tester.c.buy_stock(sender=t.k1, value=one_stock)) + assert_tx_failed(lambda: tester.c.buy_stock(sender=t.k2, value=one_stock)) def test_sell_without_stock(tester): # If you don't have any stock, you can't sell - assert_tx_failed(tester, lambda: tester.c.sell_stock(1, sender=t.k1)) - assert_tx_failed(tester, lambda: tester.c.sell_stock(1, sender=t.k2)) + assert_tx_failed(lambda: tester.c.sell_stock(1, sender=t.k1)) + assert_tx_failed(lambda: tester.c.sell_stock(1, sender=t.k2)) # Negative stock doesn't work either - assert_tx_failed(tester, lambda: tester.c.sell_stock(-1, sender=t.k1)) + assert_tx_failed(lambda: tester.c.sell_stock(-1, sender=t.k1)) # But if you do, you can! test_shares = int(tester.c.get_total_shares()) test_value = int(test_shares * tester.c.get_price()) @@ -50,44 +50,44 @@ def test_sell_without_stock(tester): assert tester.c.get_holding(t.a1) == test_shares tester.c.sell_stock(test_shares, sender=t.k1) # But only until you run out - assert_tx_failed(tester, lambda: tester.c.sell_stock(1, sender=t.k1)) + assert_tx_failed(lambda: tester.c.sell_stock(1, sender=t.k1)) def test_oversell(tester): # You can't sell more than you own test_shares = int(tester.c.get_total_shares()) test_value = int(test_shares * tester.c.get_price()) tester.c.buy_stock(sender=t.k1, value=test_value) - assert_tx_failed(tester, lambda: tester.c.sell_stock(test_shares + 1, sender=t.k1)) + assert_tx_failed(lambda: tester.c.sell_stock(test_shares + 1, sender=t.k1)) def test_transfer(tester): # If you don't have any stock, you can't transfer - assert_tx_failed(tester, lambda: tester.c.transfer_stock(t.a2, 1, sender=t.k1)) - assert_tx_failed(tester, lambda: tester.c.transfer_stock(t.a1, 1, sender=t.k2)) + assert_tx_failed(lambda: tester.c.transfer_stock(t.a2, 1, sender=t.k1)) + assert_tx_failed(lambda: tester.c.transfer_stock(t.a1, 1, sender=t.k2)) # You can't do negative transfers to gain stock - assert_tx_failed(tester, lambda: tester.c.transfer_stock(t.a2, -1, sender=t.k1)) + assert_tx_failed(lambda: tester.c.transfer_stock(t.a2, -1, sender=t.k1)) # If you transfer, you don't have the stock anymore test_shares = int(tester.c.get_total_shares()) test_value = int(test_shares * tester.c.get_price()) tester.c.buy_stock(sender=t.k1, value=test_value) assert tester.c.get_holding(t.a1) == test_shares tester.c.transfer_stock(t.a2, test_shares, sender=t.k1) - assert_tx_failed(tester, lambda: tester.c.sell_stock(1, sender=t.k1)) + assert_tx_failed(lambda: tester.c.sell_stock(1, sender=t.k1)) # But the other person does tester.c.sell_stock(test_shares, sender=t.k2) def test_paybill(tester): # Only the company can authorize payments - assert_tx_failed(tester, lambda: tester.c.pay_bill(t.a2, 1, sender=t.k1)) + assert_tx_failed(lambda: tester.c.pay_bill(t.a2, 1, sender=t.k1)) # A company can only pay someone if it has the money - assert_tx_failed(tester, lambda: tester.c.pay_bill(t.a2, 1, sender=t.k0)) + assert_tx_failed(lambda: tester.c.pay_bill(t.a2, 1, sender=t.k0)) # If it has the money, it can pay someone test_value = int(tester.c.get_total_shares() * tester.c.get_price()) tester.c.buy_stock(sender=t.k1, value=test_value) tester.c.pay_bill(t.a2, test_value, sender=t.k0) # Until it runs out of money - assert_tx_failed(tester, lambda: tester.c.pay_bill(t.a3, 1, sender=t.k0)) + assert_tx_failed(lambda: tester.c.pay_bill(t.a3, 1, sender=t.k0)) # Then no stockholders can sell their stock either - assert_tx_failed(tester, lambda: tester.c.sell_stock(1, sender=t.k1)) + assert_tx_failed(lambda: tester.c.sell_stock(1, sender=t.k1)) def test_valuation(tester): # Valuation is number of shares held times price diff --git a/tests/examples/auctions/test_simple_open_auction.py b/tests/examples/auctions/test_simple_open_auction.py index 2752b25eb7..e57b3adc47 100644 --- a/tests/examples/auctions/test_simple_open_auction.py +++ b/tests/examples/auctions/test_simple_open_auction.py @@ -16,7 +16,7 @@ def auction_tester(): @pytest.fixture def assert_tx_failed(): - def assert_tx_failed(tester, function_to_test, exception = tester.TransactionFailed): + def assert_tx_failed(function_to_test, exception = tester.TransactionFailed): initial_state = tester.s.snapshot() with pytest.raises(exception): function_to_test() @@ -41,14 +41,14 @@ def test_initial_state(auction_tester): def test_bid(auction_tester, assert_tx_failed): # Bidder cannot bid 0 - assert_tx_failed(auction_tester, lambda: auction_tester.c.bid(value=0, sender=auction_tester.k1)) + assert_tx_failed(lambda: auction_tester.c.bid(value=0, sender=auction_tester.k1)) # Bidder can bid auction_tester.c.bid(value=1, sender=tester.k1) # Check that higest bidder and highest bid have changed accordingly assert utils.remove_0x_head(auction_tester.c.get_highest_bidder()) == auction_tester.accounts[1].hex() assert auction_tester.c.get_highest_bid() == 1 # Bidder bid cannot equal current highest bid - assert_tx_failed(auction_tester, lambda: auction_tester.c.bid(value=1, sender=auction_tester.k1)) + assert_tx_failed(lambda: auction_tester.c.bid(value=1, sender=auction_tester.k1)) # Higher bid can replace current highest bid auction_tester.c.bid(value=2, sender=tester.k2) # Check that higest bidder and highest bid have changed accordingly @@ -71,7 +71,7 @@ def test_bid(auction_tester, assert_tx_failed): def test_auction_end(auction_tester, assert_tx_failed): # Fails if auction end time has not been reached - assert_tx_failed(auction_tester, lambda: auction_tester.c.auction_end()) + assert_tx_failed(lambda: auction_tester.c.auction_end()) auction_tester.c.bid(value=1 * 10**10, sender=tester.k2) # Move block timestamp foreward to reach auction end time auction_tester.s.head_state.timestamp += FIVE_DAYS @@ -81,6 +81,6 @@ def test_auction_end(auction_tester, assert_tx_failed): # Beneficiary receives the highest bid assert balance_after_end == balance_before_end + 1 * 10 ** 10 # Bidder cannot bid after auction end time has been reached - assert_tx_failed(auction_tester, lambda: auction_tester.c.bid(value=10, sender=auction_tester.k1)) + assert_tx_failed(lambda: auction_tester.c.bid(value=10, sender=auction_tester.k1)) # Auction cannot be ended twice - assert_tx_failed(auction_tester, lambda: auction_tester.c.auction_end()) + assert_tx_failed(lambda: auction_tester.c.auction_end()) diff --git a/tests/examples/safe_remote_purchase/test_safe_remote_purchase.py b/tests/examples/safe_remote_purchase/test_safe_remote_purchase.py index 97a4356a3b..47d451e166 100644 --- a/tests/examples/safe_remote_purchase/test_safe_remote_purchase.py +++ b/tests/examples/safe_remote_purchase/test_safe_remote_purchase.py @@ -33,7 +33,7 @@ def check_balance(tester): def test_initial_state(srp_tester, assert_tx_failed): assert check_balance(srp_tester) == [INIT_BAL, INIT_BAL] #Inital deposit has to be divisible by two - assert_tx_failed(srp_tester, lambda: srp_tester.s.contract(contract_code, language = "viper", args = [], value = 1)) + assert_tx_failed(lambda: srp_tester.s.contract(contract_code, language = "viper", args = [], value = 1)) #Seller puts item up for sale srp_tester.c = tester.s.contract(contract_code, language = "viper", args = [], value=2) #Check that the seller is set correctly @@ -48,20 +48,20 @@ def test_initial_state(srp_tester, assert_tx_failed): def test_abort(srp_tester, assert_tx_failed): srp_tester.c = srp_tester.s.contract(contract_code, language = "viper", args = [], value=2) #Only sender can trigger refund - assert_tx_failed(srp_tester, lambda: srp_tester.c.abort(sender=srp_tester.k2)) + assert_tx_failed(lambda: srp_tester.c.abort(sender=srp_tester.k2)) #Refund works correctly srp_tester.c.abort(sender=srp_tester.k0) assert check_balance(srp_tester) == [INIT_BAL, INIT_BAL] #Purchase in process, no refund possible srp_tester.c = srp_tester.s.contract(contract_code, language = "viper", args = [], value=2) srp_tester.c.purchase(value=2, sender=srp_tester.k1) - assert_tx_failed(srp_tester, lambda: srp_tester.c.abort(sender=srp_tester.k0)) + assert_tx_failed(lambda: srp_tester.c.abort(sender=srp_tester.k0)) def test_purchase(srp_tester, assert_tx_failed): srp_tester.c = srp_tester.s.contract(contract_code, language = "viper", args = [], value=2) #Purchase for too low/high price - assert_tx_failed(srp_tester, lambda: srp_tester.c.purchase(value=1, sender=srp_tester.k1)) - assert_tx_failed(srp_tester, lambda: srp_tester.c.purchase(value=3, sender=srp_tester.k1)) + assert_tx_failed(lambda: srp_tester.c.purchase(value=1, sender=srp_tester.k1)) + assert_tx_failed(lambda: srp_tester.c.purchase(value=3, sender=srp_tester.k1)) #Purchase for the correct price srp_tester.c.purchase(value=2, sender=srp_tester.k1) #Check if buyer is set correctly @@ -71,16 +71,16 @@ def test_purchase(srp_tester, assert_tx_failed): #Check balances, both deposits should have been deducted assert check_balance(srp_tester) == [INIT_BAL-2, INIT_BAL-2] #Allow nobody else to purchase - assert_tx_failed(srp_tester, lambda: srp_tester.c.purchase(value=2, sender=srp_tester.k3)) + assert_tx_failed(lambda: srp_tester.c.purchase(value=2, sender=srp_tester.k3)) def test_received(srp_tester, assert_tx_failed): srp_tester.c = srp_tester.s.contract(contract_code, language = "viper", args = [], value=2) #Can only be called after purchase - assert_tx_failed(srp_tester, lambda: srp_tester.c.received(sender=srp_tester.k1)) + assert_tx_failed(lambda: srp_tester.c.received(sender=srp_tester.k1)) #Purchase completed srp_tester.c.purchase(value=2, sender=srp_tester.k1) #Check that e.g. sender cannot trigger received - assert_tx_failed(srp_tester, lambda: srp_tester.c.received(sender=srp_tester.k0)) + assert_tx_failed(lambda: srp_tester.c.received(sender=srp_tester.k0)) #Check if buyer can call receive srp_tester.c.received(sender=srp_tester.k1) #Final check if everything worked. 1 value has been transferred diff --git a/tests/examples/tokens/test_vipercoin.py b/tests/examples/tokens/test_vipercoin.py index 43c566eff7..a29b5c46dd 100644 --- a/tests/examples/tokens/test_vipercoin.py +++ b/tests/examples/tokens/test_vipercoin.py @@ -56,7 +56,6 @@ def test_transfer(token_tester, assert_tx_failed): # Negative transfer value. assert_tx_failed( - tester=token_tester, function_to_test=lambda: tester.c.transfer(token_tester.accounts[1], -1), exception=ValueOutOfBounds ) @@ -92,7 +91,6 @@ def test_transferFrom(token_tester, assert_tx_failed): # Negative transfer value. assert_tx_failed( - tester=token_tester, function_to_test=lambda: contract.transferFrom(a0, a2, -1, sender=k1), exception=ValueOutOfBounds ) diff --git a/tests/examples/wallet/test_wallet.py b/tests/examples/wallet/test_wallet.py index 92f0b9b26e..a112496e8a 100644 --- a/tests/examples/wallet/test_wallet.py +++ b/tests/examples/wallet/test_wallet.py @@ -23,13 +23,13 @@ def test_approve(assert_tx_failed): to, value, data = b'\x35' * 20, 10**16, b"" assert x.approve(0, to, value, data, [sign(0, to, value, data, k) if k else [0, 0, 0] for k in (t.k1, 0, t.k3, 0, t.k5)], value=value, sender=t.k1) # Approve fails if only 2 signatures are given - assert_tx_failed(t, lambda: x.approve(1, to, value, data, [sign(1, to, value, data, k) if k else [0, 0, 0] for k in (t.k1, 0, 0, 0, t.k5)], value=value, sender=t.k1)) + assert_tx_failed(lambda: x.approve(1, to, value, data, [sign(1, to, value, data, k) if k else [0, 0, 0] for k in (t.k1, 0, 0, 0, t.k5)], value=value, sender=t.k1)) # Approve fails if an invalid signature is given - assert_tx_failed(t, lambda: x.approve(1, to, value, data, [sign(1, to, value, data, k) if k else [0, 0, 0] for k in (t.k1, 0, t.k7, 0, t.k5)], value=value, sender=t.k1)) + assert_tx_failed(lambda: x.approve(1, to, value, data, [sign(1, to, value, data, k) if k else [0, 0, 0] for k in (t.k1, 0, t.k7, 0, t.k5)], value=value, sender=t.k1)) # Approve fails if transaction number is incorrect (the first argument should be 1) - assert_tx_failed(t, lambda: x.approve(0, to, value, data, [sign(0, to, value, data, k) if k else [0, 0, 0] for k in (t.k1, 0, t.k3, 0, t.k5)], value=value, sender=t.k1)) + assert_tx_failed(lambda: x.approve(0, to, value, data, [sign(0, to, value, data, k) if k else [0, 0, 0] for k in (t.k1, 0, t.k3, 0, t.k5)], value=value, sender=t.k1)) # Approve fails if not enough value is sent - assert_tx_failed(t, lambda: x.approve(1, to, value, data, [sign(1, to, value, data, k) if k else [0, 0, 0] for k in (t.k1, 0, t.k3, 0, t.k5)], value=0, sender=t.k1)) + assert_tx_failed(lambda: x.approve(1, to, value, data, [sign(1, to, value, data, k) if k else [0, 0, 0] for k in (t.k1, 0, t.k3, 0, t.k5)], value=0, sender=t.k1)) assert x.approve(1, to, value, data, [sign(1, to, value, data, k) if k else [0, 0, 0] for k in (t.k1, 0, t.k3, 0, t.k5)], value=value, sender=t.k1) print("Basic tests passed") diff --git a/tests/parser/features/test_external_contract_calls.py b/tests/parser/features/test_external_contract_calls.py index 11c3daa45e..aba1f0529b 100644 --- a/tests/parser/features/test_external_contract_calls.py +++ b/tests/parser/features/test_external_contract_calls.py @@ -225,8 +225,8 @@ def _expr(x: address) -> num: c2._stmt(c1.address) c2._expr(c1.address) - assert_tx_failed(t, lambda: c2._stmt(c2.address)) - assert_tx_failed(t, lambda: c2._expr(c2.address)) + assert_tx_failed(lambda: c2._stmt(c2.address)) + assert_tx_failed(lambda: c2._expr(c2.address)) def test_invalid_nonexistent_contract_call(assert_tx_failed): @@ -248,8 +248,8 @@ def foo(x: address) -> num: t.s = s assert c2.foo(c1.address) == 1 - assert_tx_failed(t, lambda: c2.foo(t.a1)) - assert_tx_failed(t, lambda: c2.foo(t.a7)) + assert_tx_failed(lambda: c2.foo(t.a1)) + assert_tx_failed(lambda: c2.foo(t.a7)) def test_invalid_contract_reference_declaration(assert_tx_failed): @@ -263,7 +263,7 @@ def __init__(): pass """ t.s = t.Chain() - assert_tx_failed(t, lambda: get_contract(contract), exception = StructureException) + assert_tx_failed(lambda: get_contract(contract), exception = StructureException) def test_invalid_contract_reference_call(assert_tx_failed): @@ -272,7 +272,7 @@ def bar(arg1: address, arg2: num) -> num: return Foo(arg1).foo(arg2) """ t.s = t.Chain() - assert_tx_failed(t, lambda: get_contract(contract), exception = VariableDeclarationException) + assert_tx_failed(lambda: get_contract(contract), exception = VariableDeclarationException) def test_invalid_contract_reference_return_type(assert_tx_failed): @@ -284,7 +284,7 @@ def bar(arg1: address, arg2: num) -> num: return Foo(arg1).foo(arg2) """ t.s = t.Chain() - assert_tx_failed(t, lambda: get_contract(contract), exception = InvalidTypeException) + assert_tx_failed(lambda: get_contract(contract), exception = InvalidTypeException) def test_external_contracts_must_be_declared_first_1(assert_tx_failed): @@ -296,7 +296,7 @@ class Foo(): def foo(arg2: num) -> num: pass """ t.s = t.Chain() - assert_tx_failed(t, lambda: get_contract(contract), exception = StructureException) + assert_tx_failed(lambda: get_contract(contract), exception = StructureException) def test_external_contracts_must_be_declared_first_2(assert_tx_failed): @@ -308,7 +308,7 @@ class Foo(): def foo(arg2: num) -> num: pass """ t.s = t.Chain() - assert_tx_failed(t, lambda: get_contract(contract), exception = StructureException) + assert_tx_failed(lambda: get_contract(contract), exception = StructureException) def test_external_contracts_must_be_declared_first_3(assert_tx_failed): @@ -321,4 +321,4 @@ class Foo(): def foo(arg2: num) -> num: pass """ t.s = t.Chain() - assert_tx_failed(t, lambda: get_contract(contract), exception = StructureException) + assert_tx_failed(lambda: get_contract(contract), exception = StructureException) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index 8687238468..1ed0875020 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -261,7 +261,7 @@ def foo_(): log.MyLog('yo') """ t.s = s - assert_tx_failed(t, lambda: get_contract(loggy_code), TypeMismatchException) + assert_tx_failed(lambda: get_contract(loggy_code), TypeMismatchException) def test_fails_when_topic_is_the_wrong_size(assert_tx_failed): @@ -271,7 +271,7 @@ def foo(): log.MyLog('bars') """ t.s = s - assert_tx_failed(t, lambda: get_contract(loggy_code), TypeMismatchException) + assert_tx_failed(lambda: get_contract(loggy_code), TypeMismatchException) def test_fails_when_input_topic_is_the_wrong_size(assert_tx_failed): @@ -281,7 +281,7 @@ def foo(arg1: bytes <= 4): log.MyLog(arg1) """ t.s = s - assert_tx_failed(t, lambda: get_contract(loggy_code), TypeMismatchException) + assert_tx_failed(lambda: get_contract(loggy_code), TypeMismatchException) def test_fails_when_data_is_the_wrong_size(assert_tx_failed): @@ -291,7 +291,7 @@ def foo(): log.MyLog('bars') """ t.s = s - assert_tx_failed(t, lambda: get_contract(loggy_code), TypeMismatchException) + assert_tx_failed(lambda: get_contract(loggy_code), TypeMismatchException) def test_fails_when_input_data_is_the_wrong_size(assert_tx_failed): @@ -301,7 +301,7 @@ def foo(arg1: bytes <= 4): log.MyLog(arg1) """ t.s = s - assert_tx_failed(t, lambda: get_contract(loggy_code), TypeMismatchException) + assert_tx_failed(lambda: get_contract(loggy_code), TypeMismatchException) def test_fails_when_log_data_is_over_32_bytes(assert_tx_failed): @@ -311,7 +311,7 @@ def foo(): pass """ t.s = s - assert_tx_failed(t, lambda: get_contract(loggy_code), VariableDeclarationException) + assert_tx_failed(lambda: get_contract(loggy_code), VariableDeclarationException) def test_logging_fails_with_over_three_topics(assert_tx_failed): @@ -321,7 +321,7 @@ def __init__(): log.MyLog(1, 2, 3, 4) """ t.s = s - assert_tx_failed(t, lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) def test_logging_fails_with_duplicate_log_names(assert_tx_failed): @@ -333,7 +333,7 @@ def foo(): log.MyLog() """ t.s = s - assert_tx_failed(t, lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) def test_logging_fails_with_when_log_is_undeclared(assert_tx_failed): @@ -342,7 +342,7 @@ def foo(): log.MyLog() """ t.s = s - assert_tx_failed(t, lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) def test_logging_fails_with_topic_type_mismatch(assert_tx_failed): @@ -353,7 +353,7 @@ def foo(): log.MyLog(self) """ t.s = s - assert_tx_failed(t, lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) def test_logging_fails_with_data_type_mismatch(assert_tx_failed): @@ -364,7 +364,7 @@ def foo(): log.MyLog(self) """ t.s = s - assert_tx_failed(t, lambda: get_contract_with_gas_estimation(loggy_code), AttributeError) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), AttributeError) def test_logging_fails_after_a_global_declaration(assert_tx_failed): @@ -373,7 +373,7 @@ def test_logging_fails_after_a_global_declaration(assert_tx_failed): MyLog: __log__({arg1: bytes <= 3}) """ t.s = s - assert_tx_failed(t, lambda: get_contract_with_gas_estimation(loggy_code), StructureException) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), StructureException) def test_logging_fails_after_a_function_declaration(assert_tx_failed): @@ -384,7 +384,7 @@ def foo(): MyLog: __log__({arg1: bytes <= 3}) """ t.s = s - assert_tx_failed(t, lambda: get_contract_with_gas_estimation(loggy_code), StructureException) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), StructureException) def test_loggy_code(): diff --git a/tests/parser/types/numbers/test_num256.py b/tests/parser/types/numbers/test_num256.py index 8805adf599..5a727438ef 100644 --- a/tests/parser/types/numbers/test_num256.py +++ b/tests/parser/types/numbers/test_num256.py @@ -40,19 +40,19 @@ def _num256_le(x: num256, y: num256) -> bool: assert c._num256_add(x, y) == x + y assert c._num256_add(0,y) == y assert c._num256_add(y,0) == y - assert_tx_failed(t, lambda: c._num256_add(NUM256_MAX, NUM256_MAX)) + assert_tx_failed(lambda: c._num256_add(NUM256_MAX, NUM256_MAX)) assert c._num256_sub(x, y) == x - y - assert_tx_failed(t, lambda: c._num256_sub(y, x)) + assert_tx_failed(lambda: c._num256_sub(y, x)) assert c._num256_sub(0, 0) == 0 assert c._num256_sub(NUM256_MAX, 0) == NUM256_MAX - assert_tx_failed(t, lambda: c._num256_sub(1, 2)) + assert_tx_failed(lambda: c._num256_sub(1, 2)) assert c._num256_sub(NUM256_MAX, 1) == NUM256_MAX - 1 assert c._num256_mul(x, y) == x * y - assert_tx_failed(t, lambda: c._num256_mul(NUM256_MAX, 2)) + assert_tx_failed(lambda: c._num256_mul(NUM256_MAX, 2)) assert c._num256_mul(NUM256_MAX, 0) == 0 assert c._num256_mul(0, NUM256_MAX) == 0 assert c._num256_div(x, y) == x // y - assert_tx_failed(t, lambda: c._num256_div(NUM256_MAX, 0)) + assert_tx_failed(lambda: c._num256_div(NUM256_MAX, 0)) assert c._num256_div(y, x) == 0 assert c._num256_gt(x, y) is True assert c._num256_ge(x, y) is True @@ -87,11 +87,11 @@ def _num256_mulmod(x: num256, y: num256, z: num256) -> num256: assert c._num256_addmod(1, 2, 2) == 1 assert c._num256_addmod(32, 2, 32) == 2 assert c._num256_addmod((2**256) - 1, 0, 2) == 1 - assert_tx_failed(t, lambda: c._num256_addmod((2**256) - 1, 1, 1)) + assert_tx_failed(lambda: c._num256_addmod((2**256) - 1, 1, 1)) assert c._num256_mulmod(3, 1, 2) == 1 assert c._num256_mulmod(200, 3, 601) == 600 assert c._num256_mulmod(2**255, 1, 3) == 2 - assert_tx_failed(t, lambda: c._num256_mulmod(2**255, 2, 1)) + assert_tx_failed(lambda: c._num256_mulmod(2**255, 2, 1)) def test_num256_with_exponents(assert_tx_failed): @@ -106,7 +106,7 @@ def _num256_exp(x: num256, y: num256) -> num256: assert c._num256_exp(2, 0) == 1 assert c._num256_exp(2, 1) == 2 assert c._num256_exp(2, 3) == 8 - assert_tx_failed(t, lambda: c._num256_exp(2**128, 2)) + assert_tx_failed(lambda: c._num256_exp(2**128, 2)) assert c._num256_exp(2**64, 2) == 2**128 assert c._num256_exp(7**23, 3) == 7**69 @@ -131,13 +131,13 @@ def built_in_conversion(x: num256) -> num: assert c._num256_to_num(1) == 1 assert c._num256_to_num((2**127) - 1) == 2**127 - 1 t.s = s - assert_tx_failed(t, lambda: c._num256_to_num((2**128)) == 0) + assert_tx_failed(lambda: c._num256_to_num((2**128)) == 0) assert c._num256_to_num_call(1) == 1 # Check that casting matches manual conversion assert c._num256_to_num_call(2**127 - 1) == c.built_in_conversion(2**127 - 1) # Pass in negative int. - assert_tx_failed(t, lambda: c._num256_to_num(-1) != -1, ValueOutOfBounds) + assert_tx_failed(lambda: c._num256_to_num(-1) != -1, ValueOutOfBounds) # Make sure it can't be coherced into a negative number. - assert_tx_failed(t, lambda: c._num256_to_num_call(2**127)) + assert_tx_failed(lambda: c._num256_to_num_call(2**127)) diff --git a/tests/setup_transaction_tests.py b/tests/setup_transaction_tests.py index c39bbc54b5..15fe5faea0 100644 --- a/tests/setup_transaction_tests.py +++ b/tests/setup_transaction_tests.py @@ -126,7 +126,7 @@ def get_last_log(tester, contract, event_name=None): @pytest.fixture def assert_tx_failed(): - def assert_tx_failed(tester, function_to_test, exception = tester.TransactionFailed): + def assert_tx_failed(function_to_test, exception = tester.TransactionFailed): initial_state = tester.s.snapshot() with pytest.raises(exception): function_to_test() From c3d141e670442af9d4f717d3fff96fbf674f3df4 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Thu, 9 Nov 2017 12:18:09 +0200 Subject: [PATCH 032/162] Add 65 and 25 gas to CLAMP and ASSERT pseudo opcodes, based on test_decimals.py --- tests/parser/types/numbers/test_decimals.py | 4 ++-- viper/opcodes.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/parser/types/numbers/test_decimals.py b/tests/parser/types/numbers/test_decimals.py index 68f7522e1c..5e9bb9dbe9 100644 --- a/tests/parser/types/numbers/test_decimals.py +++ b/tests/parser/types/numbers/test_decimals.py @@ -49,7 +49,7 @@ def foop() -> num: return(floor(1999 % 1000.0)) """ - c = get_contract(decimal_test) + c = get_contract_with_gas_estimation(decimal_test) pre_txs = len(s.head_state.receipts) assert c.foo() == 999 assert c.fop() == 999 @@ -97,7 +97,7 @@ def iarg() -> wei_value: x *= 2 return x """ - c = get_contract(harder_decimal_test) + c = get_contract_with_gas_estimation(harder_decimal_test) assert c.phooey(1.2) == 20736.0 assert c.phooey(-1.2) == 20736.0 assert c.arg(-3.7) == -3.7 diff --git a/viper/opcodes.py b/viper/opcodes.py index 2e417f787a..566520f2a0 100644 --- a/viper/opcodes.py +++ b/viper/opcodes.py @@ -71,11 +71,11 @@ } pseudo_opcodes = { - 'CLAMP': [None, 3, 1, 45], + 'CLAMP': [None, 3, 1, 45 + 25], 'UCLAMPLT': [None, 2, 1, 25], 'UCLAMPLE': [None, 2, 1, 30], 'CLAMP_NONZERO': [None, 1, 1, 19], - 'ASSERT': [None, 1, 0, 20], + 'ASSERT': [None, 1, 0, 20 + 65], 'PASS': [None, 0, 0, 0], 'BREAK': [None, 0, 0, 20], 'SHA3_32': [None, 1, 1, 72], From 7be701f01f020411810762f8f7f9847568b762a8 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Thu, 9 Nov 2017 12:28:27 +0200 Subject: [PATCH 033/162] Various changes to gas estimation. - Fixes with statement estimation to total all underlying args. - Adds add_gas parameter to LLLNode to allow more advanced gas calculations to be assigned to an LLLNode, see make_byte_array_copier as an example. --- viper/parser/parser_utils.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/viper/parser/parser_utils.py b/viper/parser/parser_utils.py index f88ece3f1a..4de7e57e46 100644 --- a/viper/parser/parser_utils.py +++ b/viper/parser/parser_utils.py @@ -1,5 +1,7 @@ import re +from ethereum import opcodes + from viper.exceptions import TypeMismatchException from viper.opcodes import comb_opcodes from viper.types import ( @@ -17,7 +19,7 @@ are_units_compatible, get_size_of_type ) -from viper.utils import MemoryPositions, DECIMAL_DIVISOR +from viper.utils import MemoryPositions, DECIMAL_DIVISOR, ceil32 class NullAttractor(): @@ -33,7 +35,9 @@ def __repr__(self): # Data structure for LLL parse tree class LLLnode(): - def __init__(self, value, args=None, typ=None, location=None, pos=None, annotation='', mutable=True): + repr_show_gas = False + + def __init__(self, value, args=None, typ=None, location=None, pos=None, annotation='', mutable=True, add_gas_estimate=0): if args is None: args = [] @@ -77,9 +81,7 @@ def __init__(self, value, args=None, typ=None, location=None, pos=None, annotati self.gas += 15000 # Dynamic gas cost: calldatacopy elif self.value.upper() in ('CALLDATACOPY', 'CODECOPY'): - pass - # TODO FIX GAS ESTIMATION IN ANOTHER PR - # self.gas += ceil32(self.args[2].value) // 32 * 3 + self.gas += ceil32(self.args[2].value) // 32 * 3 # Gas limits in call if self.value.upper() == 'CALL' and isinstance(self.args[0].value, int): self.gas += self.args[0].value @@ -107,7 +109,7 @@ def __init__(self, value, args=None, typ=None, location=None, pos=None, annotati if not self.args[1].valency: raise Exception("Second argument to with statement (initial value) cannot be zerovalent: %r" % self.args[1]) self.valency = self.args[2].valency - self.gas = self.args[0].gas + self.args[1].gas + 5 + self.gas = sum([arg.gas for arg in self.args]) + 5 # Repeat statements: repeat elif self.value == 'repeat': if len(self.args[2].args) or not isinstance(self.args[2].value, int) or self.args[2].value <= 0: @@ -150,6 +152,9 @@ def __init__(self, value, args=None, typ=None, location=None, pos=None, annotati raise Exception("Invalid value for LLL AST node: %r" % self.value) assert isinstance(self.args, list) + if add_gas_estimate: + self.gas += add_gas_estimate + def to_list(self): return [self.value] + [a.to_list() for a in self.args] @@ -165,6 +170,10 @@ def repr(self): o = '' if self.annotation: o += '/* %s */ \n' % self.annotation + if self.repr_show_gas and self.gas: + OKBLUE = '\033[94m' + ENDC = '\033[0m' + o += OKBLUE + "{" + ENDC + str(self.gas) + OKBLUE + "} " + ENDC # add gas for info. o += '[' + str(self.value) prev_lineno = self.pos[0] if self.pos else None arg_lineno = None @@ -193,7 +202,7 @@ def __repr__(self): return self.repr() @classmethod - def from_list(cls, obj, typ=None, location=None, pos=None, annotation=None, mutable=True): + def from_list(cls, obj, typ=None, location=None, pos=None, annotation=None, mutable=True, add_gas_estimate=0): if isinstance(typ, str): typ = BaseType(typ) if isinstance(obj, LLLnode): @@ -203,9 +212,9 @@ def from_list(cls, obj, typ=None, location=None, pos=None, annotation=None, muta obj.location = location return obj elif not isinstance(obj, list): - return cls(obj, [], typ, location, pos, annotation, mutable) + return cls(obj, [], typ, location, pos, annotation, mutable, add_gas_estimate) else: - return cls(obj[0], [cls.from_list(o, pos=pos) for o in obj[1:]], typ, location, pos, annotation, mutable) + return cls(obj[0], [cls.from_list(o, pos=pos) for o in obj[1:]], typ, location, pos, annotation, mutable, add_gas_estimate) # Get a decimal number as a fraction with denominator multiple of 10 @@ -238,9 +247,13 @@ def make_byte_array_copier(destination, source): raise TypeMismatchException("Cannot cast from greater max-length %d to shorter max-length %d" % (source.typ.maxlen, destination.typ.maxlen)) # Special case: memory to memory if source.location == "memory" and destination.location == "memory": - return LLLnode.from_list( + gas_calculation = opcodes.GIDENTITYBASE + \ + opcodes.GIDENTITYWORD * (ceil32(source.typ.maxlen) // 32) + o = LLLnode.from_list( ['with', '_sz', ['add', 32, ['mload', source]], - ['assert', ['call', ['add', 18, ['div', '_sz', 10]], 4, 0, source, '_sz', destination, '_sz']]], typ=None) + ['assert', ['call', ['add', 18, ['div', '_sz', 10]], 4, 0, source, '_sz', destination, '_sz']]], typ=None, add_gas_estimate=gas_calculation) + return o + pos_node = LLLnode.from_list('_pos', typ=source.typ, location=source.location) # Get the length if isinstance(source.typ, NullType): From f0afae029be403f38c18d51731dc1c48159b3f61 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Thu, 9 Nov 2017 12:41:21 +0200 Subject: [PATCH 034/162] Replace get_contract with get_contract_with_gas_estimation. --- .../features/decorators/test_internal.py | 2 +- tests/parser/features/test_assignment.py | 2 +- tests/parser/features/test_clampers.py | 2 +- tests/parser/functions/test_is_contract.py | 22 +++---- tests/parser/functions/test_return_tuple.py | 4 +- tests/parser/globals/test_setters.py | 8 +-- tests/parser/types/numbers/test_num.py | 2 +- tests/parser/types/numbers/test_num256.py | 6 +- tests/parser/types/test_bytes.py | 12 ++-- tests/parser/types/test_lists.py | 4 +- tests/parser/types/test_string_literal.py | 4 +- tests/test_parser.py | 60 +++++++++---------- viper/parser/parser_utils.py | 2 +- 13 files changed, 65 insertions(+), 65 deletions(-) diff --git a/tests/parser/features/decorators/test_internal.py b/tests/parser/features/decorators/test_internal.py index c92b8e088b..28a7ef6614 100644 --- a/tests/parser/features/decorators/test_internal.py +++ b/tests/parser/features/decorators/test_internal.py @@ -13,7 +13,7 @@ def returnten() -> num: return self.a() * 2 """ - c = get_contract(internal_test) + c = get_contract_with_gas_estimation(internal_test) assert c.returnten() == 10 print("Passed internal function test") diff --git a/tests/parser/features/test_assignment.py b/tests/parser/features/test_assignment.py index e62725f7b5..f62d1641b0 100644 --- a/tests/parser/features/test_assignment.py +++ b/tests/parser/features/test_assignment.py @@ -31,7 +31,7 @@ def augmod(x: num, y: num) -> num: return z """ - c = get_contract(augassign_test) + c = get_contract_with_gas_estimation(augassign_test) assert c.augadd(5, 12) == 17 assert c.augmul(5, 12) == 60 diff --git a/tests/parser/features/test_clampers.py b/tests/parser/features/test_clampers.py index 5f3628b15a..b467a255ac 100644 --- a/tests/parser/features/test_clampers.py +++ b/tests/parser/features/test_clampers.py @@ -9,7 +9,7 @@ def foo(s: bytes <= 3) -> bytes <= 3: return s """ - c = get_contract(clamper_test_code, value=1) + c = get_contract_with_gas_estimation(clamper_test_code, value=1) assert c.foo(b"ca") == b"ca" assert c.foo(b"cat") == b"cat" try: diff --git a/tests/parser/functions/test_is_contract.py b/tests/parser/functions/test_is_contract.py index d901039a5d..c7e6b9ab14 100644 --- a/tests/parser/functions/test_is_contract.py +++ b/tests/parser/functions/test_is_contract.py @@ -14,14 +14,14 @@ def foo(arg1: address) -> bool: def foo(arg1: address) -> bool: return arg1.is_contract """ - c1 = get_contract(contract_1) - c2 = get_contract(contract_2) - - assert c1.foo(c1.address) == True - assert c1.foo(c2.address) == True - assert c1.foo(t.a1) == False - assert c1.foo(t.a3) == False - assert c2.foo(c1.address) == True - assert c2.foo(c2.address) == True - assert c2.foo(t.a1) == False - assert c2.foo(t.a3) == False + c1 = get_contract_with_gas_estimation(contract_1) + c2 = get_contract_with_gas_estimation(contract_2) + + assert c1.foo(c1.address) is True + assert c1.foo(c2.address) is True + assert c1.foo(t.a1) is False + assert c1.foo(t.a3) is False + assert c2.foo(c1.address) is True + assert c2.foo(c2.address) is True + assert c2.foo(t.a1) is False + assert c2.foo(t.a3) is False diff --git a/tests/parser/functions/test_return_tuple.py b/tests/parser/functions/test_return_tuple.py index 6142e898c7..729d6e490c 100644 --- a/tests/parser/functions/test_return_tuple.py +++ b/tests/parser/functions/test_return_tuple.py @@ -44,7 +44,7 @@ def out_very_long_bytes() -> (num, bytes <= 1024, num, address): return 5555, "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest", 6666, 0x0000000000000000000000000000000000001234 # noqa """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) assert c.out() == [3333, "0x0000000000000000000000000000000000000001"] assert c.out_literals() == [1, "0x0000000000000000000000000000000000000000", b"random"] @@ -62,5 +62,5 @@ def out_literals() -> (num, address, bytes <= 4): return 1, 0x0000000000000000000000000000000000000000, "random" """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) assert c.translator.function_data['out_literals']['decode_types'] == ['int128', 'address', 'bytes'] diff --git a/tests/parser/globals/test_setters.py b/tests/parser/globals/test_setters.py index 71d43746e7..de3732be90 100644 --- a/tests/parser/globals/test_setters.py +++ b/tests/parser/globals/test_setters.py @@ -54,7 +54,7 @@ def jop() -> num: """ - c = get_contract(multi_setter_test) + c = get_contract_with_gas_estimation(multi_setter_test) assert c.foo() == 321 assert c.fop() == 654321 assert c.goo() == 321 @@ -105,7 +105,7 @@ def gop() -> num: zed[1].bar[1].a * 1000000000000 + zed[1].bar[1].b * 10000000000000 """ - c = get_contract(multi_setter_struct_test) + c = get_contract_with_gas_estimation(multi_setter_struct_test) assert c.foo() == 654321 assert c.fop() == 87198763254321 assert c.goo() == 654321 @@ -130,7 +130,7 @@ def goo() -> num: return floor(self.pap[0][0] + self.pap[0][1] * 10 + self.pap[1][0] * 100 + self.pap[1][1] * 1000) """ - c = get_contract(type_converter_setter_test) + c = get_contract_with_gas_estimation(type_converter_setter_test) assert c.foo() == 4321 assert c.foo() == 4321 print('Passed type-conversion struct test') @@ -163,7 +163,7 @@ def foq() -> num: return popp.a[0].c + popp.a[1].c * 10 + popp.a[2].c * 100 + popp.b * 1000 """ - c = get_contract(composite_setter_test) + c = get_contract_with_gas_estimation(composite_setter_test) assert c.foo() == 4625 assert c.fop() == 4625 assert c.foq() == 4020 diff --git a/tests/parser/types/numbers/test_num.py b/tests/parser/types/numbers/test_num.py index 2f9787d97e..bc2ab7fca9 100644 --- a/tests/parser/types/numbers/test_num.py +++ b/tests/parser/types/numbers/test_num.py @@ -8,7 +8,7 @@ def _num_exp(x: num, y: num) -> num: return x**y """ - c = get_contract(exp_code) + c = get_contract_with_gas_estimation(exp_code) assert c._num_exp(2,2) == 4 assert c._num_exp(2,3) == 8 assert c._num_exp(2,4) == 16 diff --git a/tests/parser/types/numbers/test_num256.py b/tests/parser/types/numbers/test_num256.py index 8805adf599..53d1051f94 100644 --- a/tests/parser/types/numbers/test_num256.py +++ b/tests/parser/types/numbers/test_num256.py @@ -31,7 +31,7 @@ def _num256_le(x: num256, y: num256) -> bool: return num256_le(x, y) """ - c = get_contract(num256_code) + c = get_contract_with_gas_estimation(num256_code) x = 126416208461208640982146408124 y = 7128468721412412459 @@ -100,7 +100,7 @@ def _num256_exp(x: num256, y: num256) -> num256: return num256_exp(x,y) """ - c = get_contract(exp_code) + c = get_contract_with_gas_estimation(exp_code) t.s = s assert c._num256_exp(2, 0) == 1 @@ -123,7 +123,7 @@ def built_in_conversion(x: num256) -> num: return as_num128(x) """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) # Ensure uint256 function signature. assert c.translator.function_data['_num256_to_num']['encode_types'] == ['uint256'] diff --git a/tests/parser/types/test_bytes.py b/tests/parser/types/test_bytes.py index c959a4f774..499830a088 100644 --- a/tests/parser/types/test_bytes.py +++ b/tests/parser/types/test_bytes.py @@ -9,7 +9,7 @@ def foo(x: bytes <= 100) -> bytes <= 100: return x """ - c = get_contract(test_bytes) + c = get_contract_with_gas_estimation(test_bytes) moo_result = c.foo(b'cow') assert moo_result == b'cow' @@ -35,7 +35,7 @@ def foo(x: bytes <= 100) -> bytes <= 100: return y """ - c = get_contract(test_bytes2) + c = get_contract_with_gas_estimation(test_bytes2) assert c.foo(b'cow') == b'cow' assert c.foo(b'') == b'' assert c.foo(b'\x35' * 63) == b'\x35' * 63 @@ -73,7 +73,7 @@ def get_xy() -> num: return self.x * self.y """ - c = get_contract(test_bytes3) + c = get_contract_with_gas_estimation(test_bytes3) c.set_maa(b"pig") assert c.get_maa() == b"pig" assert c.get_maa2() == b"pig" @@ -104,7 +104,7 @@ def bar(inp: bytes <= 60) -> bytes <= 60: return b """ - c = get_contract(test_bytes4) + c = get_contract_with_gas_estimation(test_bytes4) assert c.foo() == b"", c.foo() assert c.bar() == b"" @@ -137,7 +137,7 @@ def quz(inp1: bytes <= 40, inp2: bytes <= 45): self.g = h """ - c = get_contract(test_bytes5) + c = get_contract_with_gas_estimation(test_bytes5) c.foo(b"cow", b"horse") assert c.check1() == b"cow" assert c.check2() == b"horse" @@ -156,7 +156,7 @@ def foo(x: bytes <= 32) -> num: return bytes_to_num(x) """ - c = get_contract(bytes_to_num_code) + c = get_contract_with_gas_estimation(bytes_to_num_code) assert c.foo(b"") == 0 try: c.foo(b"\x00") diff --git a/tests/parser/types/test_lists.py b/tests/parser/types/test_lists.py index 94ebc3213a..944d0f45f8 100644 --- a/tests/parser/types/test_lists.py +++ b/tests/parser/types/test_lists.py @@ -34,7 +34,7 @@ def loo(x: num[2][2]) -> num: return self.z2[0][0] + self.z2[0][1] + self.z3[0] * 10 + self.z3[1] * 10 """ - c = get_contract(list_tester_code) + c = get_contract_with_gas_estimation(list_tester_code) assert c.foo([3, 4, 5]) == 12 assert c.goo([[1, 2], [3, 4]]) == 73 assert c.hoo([3, 4, 5]) == 12 @@ -88,7 +88,7 @@ def roo(inp: num[2]) -> decimal[2][2]: return [inp,[3,4]] """ - c = get_contract(list_output_tester_code) + c = get_contract_with_gas_estimation(list_output_tester_code) assert c.foo() == [3, 5] assert c.goo() == [3, 5] assert c.hoo() == [3, 5] diff --git a/tests/parser/types/test_string_literal.py b/tests/parser/types/test_string_literal.py index 6ce7b5b8b7..e7e63ae96b 100644 --- a/tests/parser/types/test_string_literal.py +++ b/tests/parser/types/test_string_literal.py @@ -25,7 +25,7 @@ def baz4() -> bytes <= 100: "01234567890123456789012345678901234567890123456789") """ - c = get_contract(string_literal_code) + c = get_contract_with_gas_estimation(string_literal_code) assert c.foo() == b"horse" assert c.bar() == b"badminton" assert c.baz() == b"012345678901234567890123456789012" @@ -63,7 +63,7 @@ def baz(s: num, L: num) -> bytes <= 100: if x * y == 999: return self.moo """ % (("c" * i), ("c" * i), ("c" * i)) - c = get_contract(kode) + c = get_contract_with_gas_estimation(kode) for e in range(63, 64, 65): for _s in range(31, 32, 33): o1 = c.foo(_s, e - _s) diff --git a/tests/test_parser.py b/tests/test_parser.py index 2cda6157ec..fe33a03ba5 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -9,7 +9,7 @@ def test_block_number(): def block_number() -> num: return block.number """ - c = get_contract(block_number_code) + c = get_contract_with_gas_estimation(block_number_code) c.block_number() == 2 @@ -150,7 +150,7 @@ def bar(inp1: bytes <= 10) -> num: return x * y """ - c = get_contract(test_slice) + c = get_contract_with_gas_estimation(test_slice) x = c.foo(b"badminton") assert x == b"min", x @@ -168,7 +168,7 @@ def slice_tower_test(inp1: bytes <= 50) -> bytes <= 50: return inp """ - c = get_contract(test_slice2) + c = get_contract_with_gas_estimation(test_slice2) x = c.slice_tower_test(b"abcdefghijklmnopqrstuvwxyz1234") assert x == b"klmnopqrst", x @@ -193,7 +193,7 @@ def bar(inp1: bytes <= 50) -> num: return self.x * self.y """ - c = get_contract(test_slice3) + c = get_contract_with_gas_estimation(test_slice3) x = c.foo(b"badminton") assert x == b"min", x @@ -208,7 +208,7 @@ def foo(inp: bytes <= 10, start: num, len: num) -> bytes <= 10: return slice(inp, start=start, len=len) """ - c = get_contract(test_slice4) + c = get_contract_with_gas_estimation(test_slice4) assert c.foo(b"badminton", 3, 3) == b"min" assert c.foo(b"badminton", 0, 9) == b"badminton" assert c.foo(b"badminton", 1, 8) == b"adminton" @@ -248,7 +248,7 @@ def foo(inp: bytes <= 10) -> num: return len(inp) * 100 + len(x) * 10 + len(self.y) """ - c = get_contract(test_length) + c = get_contract_with_gas_estimation(test_length) assert c.foo(b"badminton") == 954, c.foo(b"badminton") print('Passed length test') @@ -262,7 +262,7 @@ def foo3(input1: bytes <= 50, input2: bytes <= 50, input3: bytes <= 50) -> bytes return concat(input1, input2, input3) """ - c = get_contract(test_concat) + c = get_contract_with_gas_estimation(test_concat) assert c.foo2(b"h", b"orse") == b"horse" assert c.foo2(b"h", b"") == b"h" assert c.foo2(b"", b"") == b"" @@ -281,7 +281,7 @@ def foo(inp: bytes <= 50) -> bytes <= 1000: return concat(x, inp, x, inp, x, inp, x, inp, x, inp) """ - c = get_contract(test_concat2) + c = get_contract_with_gas_estimation(test_concat2) assert c.foo(b"horse" * 9 + b"viper") == (b"horse" * 9 + b"viper") * 10 print('Passed second concat test') @@ -296,7 +296,7 @@ def krazykonkat(z: bytes <= 10) -> bytes <= 25: return concat(x, " ", self.y, " ", z) """ - c = get_contract(crazy_concat_code) + c = get_contract_with_gas_estimation(crazy_concat_code) assert c.krazykonkat(b"moose") == b'cow horse moose' @@ -312,7 +312,7 @@ def bar() -> bytes32: return sha3("inp") """ - c = get_contract(hash_code) + c = get_contract_with_gas_estimation(hash_code) for inp in (b"", b"cow", b"s" * 31, b"\xff" * 32, b"\n" * 33, b"g" * 64, b"h" * 65): assert c.foo(inp) == u.sha3(inp) @@ -324,7 +324,7 @@ def test_hash_code2(): def foo(inp: bytes <= 100) -> bool: return sha3(inp) == sha3("badminton") """ - c = get_contract(hash_code2) + c = get_contract_with_gas_estimation(hash_code2) assert c.foo(b"badminto") is False assert c.foo(b"badminton") is True @@ -345,7 +345,7 @@ def trymem(inp: bytes <= 100) -> bool: def try32(inp: bytes32) -> bool: return sha3(inp) == sha3(self.test) """ - c = get_contract(hash_code3) + c = get_contract_with_gas_estimation(hash_code3) c.set_test(b"") assert c.tryy(b"") is True assert c.trymem(b"") is True @@ -376,7 +376,7 @@ def returnten() -> num: ans = raw_call(self, concat(method_id("double(int128)"), as_bytes32(5)), gas=50000, outsize=32) return as_num128(extract32(ans, 0)) """ - c = get_contract(method_id_test) + c = get_contract_with_gas_estimation(method_id_test) assert c.returnten() == 10 print("Passed method ID test") @@ -393,7 +393,7 @@ def test_ecrecover2() -> address: as_num256(6577251522710269046055727877571505144084475024240851440410274049870970796685)) """ - c = get_contract(ecrecover_test) + c = get_contract_with_gas_estimation(ecrecover_test) h = b'\x35' * 32 k = b'\x46' * 32 v, r, S = u.ecsign(h, k) @@ -462,7 +462,7 @@ def fivetimes(inp: bytes32) -> bytes <= 160: return concat(inp, inp, inp, inp, inp) """ - c = get_contract(test_concat_bytes32) + c = get_contract_with_gas_estimation(test_concat_bytes32) assert c.sandwich(b"cow", b"\x35" * 32) == b"\x35" * 32 + b"cow" + b"\x35" * 32, c.sandwich(b"cow", b"\x35" * 32) assert c.sandwich(b"", b"\x46" * 32) == b"\x46" * 64 assert c.sandwich(b"\x57" * 95, b"\x57" * 32) == b"\x57" * 159 @@ -485,7 +485,7 @@ def baz() -> bytes <= 7: return raw_call(0x0000000000000000000000000000000000000004, "moose", gas=50000, outsize=7) """ - c = get_contract(caller_code) + c = get_contract_with_gas_estimation(caller_code) assert c.foo() == b"moose" assert c.bar() == b"moo" assert c.baz() == b"moose\x00\x00" @@ -511,7 +511,7 @@ def foq(inp: bytes <= 32) -> address: return extract32(inp, 0, type=address) """ - c = get_contract(extract32_code) + c = get_contract_with_gas_estimation(extract32_code) assert c.foo(b"\x00" * 30 + b"\x01\x01") == 257 assert c.bar(b"\x00" * 30 + b"\x01\x01") == 257 try: @@ -587,7 +587,7 @@ def voo(inp: bytes <= 1024) -> num: x = RLPList(inp, [num, num, bytes32, num, bytes32, bytes]) return x[1] """ - c = get_contract(rlp_decoder_code) + c = get_contract_with_gas_estimation(rlp_decoder_code) assert c.foo() == '0x' + '35' * 20 assert c.fop() == b'G' * 32 @@ -660,7 +660,7 @@ def hoo(x: bytes32, y: bytes32) -> bytes <= 64: return concat(x, y) """ - c = get_contract(konkat_code) + c = get_contract_with_gas_estimation(konkat_code) assert c.foo(b'\x35' * 32, b'\x00' * 32) == b'\x35' * 32 + b'\x00' * 32 assert c.goo(b'\x35' * 32, b'\x00' * 32) == b'\x35' * 32 + b'\x00' * 32 assert c.hoo(b'\x35' * 32, b'\x00' * 32) == b'\x35' * 32 + b'\x00' * 32 @@ -694,9 +694,9 @@ def foo() -> num: return 5 """ - c = get_contract(large_input_code_2, args=[17], sender=t.k0, value=0) + c = get_contract_with_gas_estimation(large_input_code_2, args=[17], sender=t.k0, value=0) try: - c = get_contract(large_input_code_2, args=[2**130], sender=t.k0, value=0) + c = get_contract_with_gas_estimation(large_input_code_2, args=[2**130], sender=t.k0, value=0) success = True except: success = False @@ -726,7 +726,7 @@ def _shift(x: num256, y: num) -> num256: return shift(x, y) """ - c = get_contract(test_bitwise) + c = get_contract_with_gas_estimation(test_bitwise) x = 126416208461208640982146408124 y = 7128468721412412459 assert c._bitwise_and(x, y) == (x & y) @@ -760,7 +760,7 @@ def returnten() -> num: return self._len("badminton!") """ - c = get_contract(selfcall_code_3) + c = get_contract_with_gas_estimation(selfcall_code_3) assert c.return_hash_of_cow_x_30() == u.sha3(b'cow' * 30) assert c.returnten() == 10 @@ -773,7 +773,7 @@ def returnten() -> num: return 10 """ - c = get_contract(inner_code) + c = get_contract_with_gas_estimation(inner_code) outer_code = """ def create_and_call_returnten(inp: address) -> num: @@ -836,7 +836,7 @@ def goo() -> num256: return num256_add(min(as_num256(3), as_num256(5)), max(as_num256(40), as_num256(80))) """ - c = get_contract(minmax_test) + c = get_contract_with_gas_estimation(minmax_test) assert c.foo() == 58223.123 assert c.goo() == 83 @@ -846,7 +846,7 @@ def test_ecadd(): ecadder = """ x3: num256[2] y3: num256[2] - + def _ecadd(x: num256[2], y: num256[2]) -> num256[2]: return ecadd(x, y) @@ -861,7 +861,7 @@ def _ecadd3(x: num256[2], y: num256[2]) -> num256[2]: return ecadd(self.x3, self.y3) """ - c = get_contract(ecadder) + c = get_contract_with_gas_estimation(ecadder) assert c._ecadd(G1, G1) == G1_times_two assert c._ecadd2(G1, G1_times_two) == G1_times_three @@ -872,7 +872,7 @@ def test_ecmul(): ecmuller = """ x3: num256[2] y3: num256 - + def _ecmul(x: num256[2], y: num256) -> num256[2]: return ecmul(x, y) @@ -887,7 +887,7 @@ def _ecmul3(x: num256[2], y: num256) -> num256[2]: return ecmul(self.x3, self.y3) """ - c = get_contract(ecmuller) + c = get_contract_with_gas_estimation(ecmuller) assert c._ecmul(G1, 0) == [0 ,0] assert c._ecmul(G1, 1) == G1 @@ -906,6 +906,6 @@ def exp(base: num256, exponent: num256, modulus: num256) -> num256: return o """ - c = get_contract(modexper) + c = get_contract_with_gas_estimation(modexper) assert c.exp(3, 5, 100) == 43 assert c.exp(2, 997, 997) == 2 diff --git a/viper/parser/parser_utils.py b/viper/parser/parser_utils.py index 4de7e57e46..6ec41d87d7 100644 --- a/viper/parser/parser_utils.py +++ b/viper/parser/parser_utils.py @@ -129,7 +129,7 @@ def __init__(self, value, args=None, typ=None, location=None, pos=None, annotati # Seq statements: seq ... elif self.value == 'seq': self.valency = self.args[-1].valency if self.args else 0 - self.gas = sum([arg.gas for arg in self.args]) + self.gas = sum([arg.gas for arg in self.args]) + 30 # Multi statements: multi ... elif self.value == 'multi': for arg in self.args: From ea09106f74255588d4f85ffb4cd00f7b956dc7e8 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Thu, 9 Nov 2017 15:19:36 +0200 Subject: [PATCH 035/162] Adds test for keys with a very long byte length. --- tests/parser/features/test_bytes_map_keys.py | 21 +++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/parser/features/test_bytes_map_keys.py b/tests/parser/features/test_bytes_map_keys.py index f4fd495f5c..d9cb349c83 100644 --- a/tests/parser/features/test_bytes_map_keys.py +++ b/tests/parser/features/test_bytes_map_keys.py @@ -61,6 +61,25 @@ def get(k: bytes <= 34) -> num: assert c.get("a" * 34) == 6789 +def test_basic_very_long_bytes_as_keys(): + code = """ +mapped_bytes: num[bytes <= 4096] + +def set(k: bytes <= 4096, v: num): + self.mapped_bytes[k] = v + + +def get(k: bytes <= 4096) -> num: + return self.mapped_bytes[k] + """ + + c = get_contract(code) + + c.set("test" * 1024, 6789) + + assert c.get("test" * 1024) == 6789 + + def test_mismatched_byte_length(): code = """ mapped_bytes: num[bytes <= 34] @@ -70,4 +89,4 @@ def set(k: bytes <= 35, v: num): """ with pytest.raises(TypeMismatchException): - c = get_contract(code) + get_contract(code) From f6f6eb19b9c0121b163ec34ebcb93f8e3dd0836f Mon Sep 17 00:00:00 2001 From: chgue Date: Thu, 9 Nov 2017 20:04:03 +0100 Subject: [PATCH 036/162] Removed compound calling, small touchups --- docs/types.rst | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 8dd13cb97f..4b4d1b4364 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -48,15 +48,9 @@ Operator Description ``x or y`` Logical disjunction ``x == y`` Equality ``x != y`` Inequality -=================== =================== +==================== =================== -The operators ``or`` and ``and`` apply the common short-circuiting rules: -:: - #Short-circuiting - return false and foo() - #Returns false without calling foo() since it is not necessary for the result - return true or bar() - #Returns true without calling bar() since it is not necessary for the result +The operators ``or`` and ``and`` apply the common short-circuiting rules. .. index:: ! num, ! int, ! integer Signed Integer (128 bit) @@ -245,7 +239,7 @@ Syntax as follows: ``_address.``, where ``_address`` is of the type ``ad Unit Types ========== -Viper allows the definition of types with a discrete unit such as e.g. meters, seconds, wei, ... . These types may only be based on either ``num`` or ``decimal``. +Viper allows the definition of types with discrete units e.g. meters, seconds, wei, ... . These types may only be based on either ``num`` or ``decimal``. Viper has multiple unit types built in, which are the following: ============= ===== ========= ========================== From 85e3898e9c491206af904ebd9bc50998a6951f50 Mon Sep 17 00:00:00 2001 From: Neil Date: Fri, 10 Nov 2017 13:16:06 +0200 Subject: [PATCH 037/162] Minor spelling fix --- examples/auctions/simple_open_auction.v.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/auctions/simple_open_auction.v.py b/examples/auctions/simple_open_auction.v.py index 557e017b9e..c270ce760a 100644 --- a/examples/auctions/simple_open_auction.v.py +++ b/examples/auctions/simple_open_auction.v.py @@ -1,7 +1,7 @@ # Open Auction # Auction params -# Beneficiary recieves money from the highest bidder +# Beneficiary receives money from the highest bidder beneficiary: public(address) auction_start: public(timestamp) auction_end: public(timestamp) From b014a4bc9375d7797fd56d42b10199aae9312345 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sun, 22 Oct 2017 18:18:23 -0600 Subject: [PATCH 038/162] Remove empty test --- tests/parser/syntax/test_byte_string.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/parser/syntax/test_byte_string.py b/tests/parser/syntax/test_byte_string.py index 09fb8ca9ab..7321d03100 100644 --- a/tests/parser/syntax/test_byte_string.py +++ b/tests/parser/syntax/test_byte_string.py @@ -5,18 +5,6 @@ from viper.exceptions import TypeMismatchException -fail_list = [ - -] - - -@pytest.mark.parametrize('bad_code', fail_list) -def test_byte_string_fail(bad_code): - - with raises(TypeMismatchException): - compiler.compile(bad_code) - - valid_list = [ """ def foo() -> bytes <= 10: From 6bd257e54031bfab4db476dd68b35358ddae4d97 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sun, 29 Oct 2017 17:18:31 -0500 Subject: [PATCH 039/162] Refactor code parsing --- viper/parser/parser.py | 74 ++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 197bed0f01..fb7718a7a2 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -290,6 +290,40 @@ def mk_full_signature(code): return o +def parse_events(sigs, _events): + for event in _events: + sigs[event.target.id] = EventSignature.from_declaration(event) + return sigs + + +def parse_external_contracts(external_contracts, _contracts): + for _contractname in _contracts: + _contract_defs = _contracts[_contractname] + _defnames = [_def.name for _def in _contract_defs] + contract = {} + if len(set(_defnames)) < len(_contract_defs): + raise VariableDeclarationException("Duplicate function name: %s" % [name for name in _defnames if _defnames.count(name) > 1][0]) + for _def in _contract_defs: + sig = FunctionSignature.from_definition(_def) + contract[sig.name] = sig + external_contracts[_contractname] = contract + return external_contracts + + +def parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, origcode): + sub = ['seq', initializer_lll] + add_gas = initializer_lll.gas + for _def in otherfuncs: + sub.append(parse_func(_def, _globals, {**{'self': sigs}, **external_contracts}, origcode)) + sub[-1].total_gas += add_gas + add_gas += 30 + sig = FunctionSignature.from_definition(_def) + sig.gas = sub[-1].total_gas + sigs[sig.name] = sig + o.append(['return', 0, ['lll', sub, 0]]) + return o + + # Main python parse tree => LLL method def parse_tree_to_lll(code, origcode): _contracts, _events, _defs, _globals = get_contracts_and_defs_and_globals(code) @@ -297,48 +331,25 @@ def parse_tree_to_lll(code, origcode): # Checks for duplicate funciton / event names if len(set(_names)) < len(_names): raise VariableDeclarationException("Duplicate function or event name: %s" % [name for name in _names if _names.count(name) > 1][0]) - contracts = {} - # Create the main statement - o = ['seq'] # Initialization function initfunc = [_def for _def in _defs if is_initializer(_def)] # Regular functions otherfuncs = [_def for _def in _defs if not is_initializer(_def)] sigs = {} + external_contracts = {} + # Create the main statement + o = ['seq'] if _events: - for event in _events: - sigs[event.target.id] = EventSignature.from_declaration(event) - for _contractname in _contracts: - _c_defs = _contracts[_contractname] - _defnames = [_def.name for _def in _c_defs] - contract = {} - if len(set(_defnames)) < len(_c_defs): - raise VariableDeclarationException("Duplicate function name: %s" % [name for name in _defnames if _defnames.count(name) > 1][0]) - c_otherfuncs = [_def for _def in _c_defs if not is_initializer(_def)] - if c_otherfuncs: - for _def in c_otherfuncs: - sig = FunctionSignature.from_definition(_def) - contract[sig.name] = sig - contracts[_contractname] = contract - _defnames = [_def.name for _def in _defs] - if len(set(_defnames)) < len(_defs): - raise VariableDeclarationException("Duplicate function name: %s" % [name for name in _defnames if _defnames.count(name) > 1][0]) + sigs = parse_events(sigs, _events) + if _contracts: + external_contracts = parse_external_contracts(external_contracts, _contracts) # If there is an init func... if initfunc: o.append(['seq', initializer_lll]) - o.append(parse_func(initfunc[0], _globals, {**{'self': sigs}, **contracts}, origcode)) + o.append(parse_func(initfunc[0], _globals, {**{'self': sigs}, **external_contracts}, origcode)) # If there are regular functions... if otherfuncs: - sub = ['seq', initializer_lll] - add_gas = initializer_lll.gas - for _def in otherfuncs: - sub.append(parse_func(_def, _globals, {**{'self': sigs}, **contracts}, origcode)) - sub[-1].total_gas += add_gas - add_gas += 30 - sig = FunctionSignature.from_definition(_def) - sig.gas = sub[-1].total_gas - sigs[sig.name] = sig - o.append(['return', 0, ['lll', sub, 0]]) + o = parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, origcode) return LLLnode.from_list(o, typ=None) @@ -382,7 +393,6 @@ def make_clamper(datapos, mempos, typ, is_init=False): def parse_func(code, _globals, sigs, origcode, _vars=None): if _vars is None: _vars = {} - sig = FunctionSignature.from_definition(code) # Check for duplicate variables with globals for arg in sig.args: From 2597c18ca3a47f9efc07bc9e839fc89fe3d13210 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sun, 29 Oct 2017 17:19:35 -0500 Subject: [PATCH 040/162] Fix typo in folder name --- tests/parser/{intergration => integration}/test_basics.py | 0 tests/parser/{intergration => integration}/test_crowdfund.py | 0 tests/parser/{intergration => integration}/test_escrow.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tests/parser/{intergration => integration}/test_basics.py (100%) rename tests/parser/{intergration => integration}/test_crowdfund.py (100%) rename tests/parser/{intergration => integration}/test_escrow.py (100%) diff --git a/tests/parser/intergration/test_basics.py b/tests/parser/integration/test_basics.py similarity index 100% rename from tests/parser/intergration/test_basics.py rename to tests/parser/integration/test_basics.py diff --git a/tests/parser/intergration/test_crowdfund.py b/tests/parser/integration/test_crowdfund.py similarity index 100% rename from tests/parser/intergration/test_crowdfund.py rename to tests/parser/integration/test_crowdfund.py diff --git a/tests/parser/intergration/test_escrow.py b/tests/parser/integration/test_escrow.py similarity index 100% rename from tests/parser/intergration/test_escrow.py rename to tests/parser/integration/test_escrow.py From ef62cc563dad599b1846f9a5376003ff9797bbec Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Mon, 13 Nov 2017 10:56:11 +0200 Subject: [PATCH 041/162] Make pseudo opcode gas estimate a single value. --- viper/opcodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/viper/opcodes.py b/viper/opcodes.py index 566520f2a0..d1ed4df9ae 100644 --- a/viper/opcodes.py +++ b/viper/opcodes.py @@ -71,11 +71,11 @@ } pseudo_opcodes = { - 'CLAMP': [None, 3, 1, 45 + 25], + 'CLAMP': [None, 3, 1, 70], 'UCLAMPLT': [None, 2, 1, 25], 'UCLAMPLE': [None, 2, 1, 30], 'CLAMP_NONZERO': [None, 1, 1, 19], - 'ASSERT': [None, 1, 0, 20 + 65], + 'ASSERT': [None, 1, 0, 85], 'PASS': [None, 0, 0, 0], 'BREAK': [None, 0, 0, 20], 'SHA3_32': [None, 1, 1, 72], From 8468c4c0962f53fcfe6d89d62e51d17c5a384869 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Mon, 13 Nov 2017 10:56:37 +0200 Subject: [PATCH 042/162] Add test for colored gas estimate output when using -f ir. --- bin/viper | 6 ++++++ tests/parser/features/test_gas.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/bin/viper b/bin/viper index 671211183d..bffde9b087 100755 --- a/bin/viper +++ b/bin/viper @@ -6,10 +6,13 @@ import viper from viper import compiler, optimizer from viper.parser.parser import parse_to_lll +from viper.parser import parser_utils + parser = argparse.ArgumentParser(description='Viper {0} programming language for Ethereum'.format(viper.__version__)) parser.add_argument('input_file', help='Viper sourcecode to compile') parser.add_argument('-f', help='Format to print', choices=['abi', 'json', 'bytecode', 'ir'], default='bytecode', dest='format') +parser.add_argument('--show-gas-estimates', help='Show gas estimates in ir output mode.', action="store_true") args = parser.parse_args() @@ -17,6 +20,9 @@ if __name__ == '__main__': with open(args.input_file) as fh: code = fh.read() + if args.show_gas_estimates: + parser_utils.LLLnode.repr_show_gas = True + if args.format == 'abi': print(compiler.mk_full_signature(code)) elif args.format == 'json': diff --git a/tests/parser/features/test_gas.py b/tests/parser/features/test_gas.py index ef70a501d8..c79baadc9b 100644 --- a/tests/parser/features/test_gas.py +++ b/tests/parser/features/test_gas.py @@ -2,6 +2,9 @@ from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ get_contract_with_gas_estimation, get_contract +from viper.parser.parser import parse_to_lll +from viper.parser import parser_utils + def test_gas_call(): gas_call = """ @@ -15,3 +18,17 @@ def foo() -> num: assert c.foo(startgas = 50000) < 50000 assert c.foo(startgas = 50000) > 25000 print('Passed gas test') + + +def test_gas_estimate_repr(): + code = """ +x: num + + +def __init__(): + self.x = 1 + """ + parser_utils.LLLnode.repr_show_gas = True + out = parse_to_lll(code) + assert str(out)[:30] == '\x1b[94m{\x1b[0m20303\x1b[94m} \x1b[0m[seq' + parser_utils.LLLnode.repr_show_gas = False From efe6c4c38e2b02838682794b736015a2fe901c56 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Mon, 13 Nov 2017 11:07:26 +0200 Subject: [PATCH 043/162] Remove tester from assert_tx_failed. --- tests/parser/features/iteration/test_range_in.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/parser/features/iteration/test_range_in.py b/tests/parser/features/iteration/test_range_in.py index a5af619837..bd3db4c000 100644 --- a/tests/parser/features/iteration/test_range_in.py +++ b/tests/parser/features/iteration/test_range_in.py @@ -96,7 +96,7 @@ def is_owner() -> bool: assert c.is_owner(sender=t.k1) is False # no one else is. # only an owner may set another owner. - assert_tx_failed(t, lambda: c.set_owner(1, t.a1, sender=t.k1)) + assert_tx_failed(lambda: c.set_owner(1, t.a1, sender=t.k1)) c.set_owner(1, t.a1) assert c.is_owner(sender=t.k1) is True From 557acfbec78383916c5c5fa276d93886877ad446 Mon Sep 17 00:00:00 2001 From: David Knott Date: Mon, 13 Nov 2017 16:15:19 -0700 Subject: [PATCH 044/162] Move test_company file into proper folder --- .../examples/company}/test_company.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) rename {examples/stock => tests/examples/company}/test_company.py (91%) diff --git a/examples/stock/test_company.py b/tests/examples/company/test_company.py similarity index 91% rename from examples/stock/test_company.py rename to tests/examples/company/test_company.py index 2c0008a1dc..0c13672b31 100644 --- a/examples/stock/test_company.py +++ b/tests/examples/company/test_company.py @@ -2,8 +2,8 @@ from ethereum.tools import tester as t from ethereum import utils - from viper import compiler +from tests.setup_transaction_tests import assert_tx_failed @pytest.fixture def tester(): @@ -18,14 +18,7 @@ def tester(): args=[tester.company_address, 1000, 10**6]) return tester -@pytest.fixture -def assert_tx_failed(function_to_test, exception = t.TransactionFailed): - initial_state = tester.s.snapshot() - with pytest.raises(exception): - function_to_test() - tester.s.revert(initial_state) - -def test_overbuy(tester): +def test_overbuy(tester, assert_tx_failed): # If all the stock has been bought, no one can buy more test_shares = int(tester.c.get_total_shares() / 2) test_value = int(test_shares * tester.c.get_price()) @@ -37,7 +30,7 @@ def test_overbuy(tester): assert_tx_failed(lambda: tester.c.buy_stock(sender=t.k1, value=one_stock)) assert_tx_failed(lambda: tester.c.buy_stock(sender=t.k2, value=one_stock)) -def test_sell_without_stock(tester): +def test_sell_without_stock(tester, assert_tx_failed): # If you don't have any stock, you can't sell assert_tx_failed(lambda: tester.c.sell_stock(1, sender=t.k1)) assert_tx_failed(lambda: tester.c.sell_stock(1, sender=t.k2)) @@ -52,14 +45,14 @@ def test_sell_without_stock(tester): # But only until you run out assert_tx_failed(lambda: tester.c.sell_stock(1, sender=t.k1)) -def test_oversell(tester): +def test_oversell(tester, assert_tx_failed): # You can't sell more than you own test_shares = int(tester.c.get_total_shares()) test_value = int(test_shares * tester.c.get_price()) tester.c.buy_stock(sender=t.k1, value=test_value) assert_tx_failed(lambda: tester.c.sell_stock(test_shares + 1, sender=t.k1)) -def test_transfer(tester): +def test_transfer(tester, assert_tx_failed): # If you don't have any stock, you can't transfer assert_tx_failed(lambda: tester.c.transfer_stock(t.a2, 1, sender=t.k1)) assert_tx_failed(lambda: tester.c.transfer_stock(t.a1, 1, sender=t.k2)) @@ -75,7 +68,7 @@ def test_transfer(tester): # But the other person does tester.c.sell_stock(test_shares, sender=t.k2) -def test_paybill(tester): +def test_paybill(tester, assert_tx_failed): # Only the company can authorize payments assert_tx_failed(lambda: tester.c.pay_bill(t.a2, 1, sender=t.k1)) # A company can only pay someone if it has the money From d189d41f8a8b92a9d4a77327a74c52b320bf683e Mon Sep 17 00:00:00 2001 From: David Knott Date: Sat, 11 Nov 2017 14:09:19 -0700 Subject: [PATCH 045/162] Fix constant clamp checks / change constant clamp checks to compile time --- viper/compile_lll.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/viper/compile_lll.py b/viper/compile_lll.py index bc804834e2..f29a4c9032 100644 --- a/viper/compile_lll.py +++ b/viper/compile_lll.py @@ -156,10 +156,14 @@ def compile_to_assembly(code, withargs=None, break_dest=None, height=0): # Unsigned/signed clamp, check less-than elif code.value in ('uclamplt', 'uclample', 'clamplt', 'clample', 'uclampgt', 'uclampge', 'clampgt', 'clampge'): if isinstance(code.args[0].value, int) and isinstance(code.args[1].value, int): - if 0 <= code.args[0].value < code.args[1].value: + # Checks for clamp errors at compile time as opposed to run time + if code.value in ('uclamplt', 'clamplt') and 0 <= code.args[0].value < code.args[1].value or \ + code.value in ('uclample', 'clample') and 0 <= code.args[0].value <= code.args[1].value or \ + code.value in ('uclampgt', 'clampgt') and 0 <= code.args[0].value > code.args[1].value or \ + code.value in ('uclampge', 'clampge') and 0 <= code.args[0].value >= code.args[1].value: return compile_to_assembly(code.args[0], withargs, break_dest, height) else: - return ['INVALID'] + raise Exception("Invalid %r with values %r and %r" % (code.value, code.args[0], code.args[1])) o = compile_to_assembly(code.args[0], withargs, break_dest, height) o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height + 1)) o.extend(['DUP2']) From 5f7ee7561d5405fc3cae682cb757f1836d62d4c3 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sat, 11 Nov 2017 14:09:44 -0700 Subject: [PATCH 046/162] Add fixture to directly test LLL --- tests/conftest.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 64bb081d3c..b2634e08b7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,28 @@ import pytest +from ethereum.tools import tester +from viper.parser.parser_utils import ( + LLLnode +) +from viper import ( + compile_lll, + optimizer +) @pytest.fixture def bytes_helper(): def bytes_helper(str, length): return bytes(str, 'utf-8') + bytearray(length-len(str)) return bytes_helper + +@pytest.fixture +def t(): + tester.s = tester.Chain() + return tester + +@pytest.fixture +def get_contract_from_lll(t): + def lll_compiler(lll): + lll = optimizer.optimize(LLLnode.from_list(lll)) + byte_code = compile_lll.assembly_to_evm(compile_lll.compile_to_assembly(lll)) + t.s.tx(to=b'', data=byte_code) + return lll_compiler From 997c3eac5cc34f8e00e656ccda86372b67b7125b Mon Sep 17 00:00:00 2001 From: David Knott Date: Sat, 11 Nov 2017 14:09:54 -0700 Subject: [PATCH 047/162] Add clamp tests at the LLL level --- tests/compiler/test_clamps.py | 97 +++++++++++++++++++++++++++++++++++ tests/conftest.py | 5 ++ 2 files changed, 102 insertions(+) create mode 100644 tests/compiler/test_clamps.py diff --git a/tests/compiler/test_clamps.py b/tests/compiler/test_clamps.py new file mode 100644 index 0000000000..74b379c827 --- /dev/null +++ b/tests/compiler/test_clamps.py @@ -0,0 +1,97 @@ +import pytest + + +def test_uclamplt(t, get_contract_from_lll, assert_compile_failed): + lll = ['uclamplt', 2, 1] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['uclamplt', 1, 1] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['uclamplt', 0, 1] + get_contract_from_lll(lll) + + +def test_uclample(t, get_contract_from_lll, assert_compile_failed): + lll = ['uclample', 2, 1] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['uclample', 1, 1] + get_contract_from_lll(lll) + lll = ['uclample', 0, 1] + get_contract_from_lll(lll) + + +def test_uclampgt(t, get_contract_from_lll, assert_compile_failed): + lll = ['uclampgt', 1, 2] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['uclampgt', 1, 1] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['uclampgt', 1, 0] + get_contract_from_lll(lll) + + +def test_uclampge(t, get_contract_from_lll, assert_compile_failed): + lll = ['uclampge', 1, 2] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['uclampge', 1, 1] + get_contract_from_lll(lll) + lll = ['uclampge', 1, 0] + get_contract_from_lll(lll) + + +def test_uclamplt_and_clamplt(t, get_contract_from_lll, assert_compile_failed): + lll = ['uclamplt', 2, 1] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['uclamplt', 1, 1] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['uclamplt', 0, 1] + get_contract_from_lll(lll) + lll = ['clamplt', 2, 1] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['clamplt', 1, 1] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['clamplt', 0, 1] + get_contract_from_lll(lll) + + +def test_uclample_clample(t, get_contract_from_lll, assert_compile_failed): + lll = ['uclample', 2, 1] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['uclample', 1, 1] + get_contract_from_lll(lll) + lll = ['uclample', 0, 1] + get_contract_from_lll(lll) + lll = ['clample', 2, 1] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['clample', 1, 1] + get_contract_from_lll(lll) + lll = ['clample', 0, 1] + get_contract_from_lll(lll) + + +def test_uclampgt_and_clampgt(t, get_contract_from_lll, assert_compile_failed): + lll = ['uclampgt', 1, 2] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['uclampgt', 1, 1] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['uclampgt', 1, 0] + get_contract_from_lll(lll) + lll = ['clampgt', 1, 2] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['clampgt', 1, 1] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['clampgt', 1, 0] + get_contract_from_lll(lll) + + +def test_uclampge_and_clampge(t, get_contract_from_lll, assert_compile_failed): + lll = ['uclampge', 1, 2] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['uclampge', 1, 1] + get_contract_from_lll(lll) + lll = ['uclampge', 1, 0] + get_contract_from_lll(lll) + lll = ['clampge', 1, 2] + assert_compile_failed(lambda: get_contract_from_lll(lll), Exception) + lll = ['clampge', 1, 1] + get_contract_from_lll(lll) + lll = ['clampge', 1, 0] + get_contract_from_lll(lll) diff --git a/tests/conftest.py b/tests/conftest.py index b2634e08b7..334a728b5e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,7 @@ compile_lll, optimizer ) +from tests.setup_transaction_tests import assert_tx_failed @pytest.fixture def bytes_helper(): @@ -26,3 +27,7 @@ def lll_compiler(lll): byte_code = compile_lll.assembly_to_evm(compile_lll.compile_to_assembly(lll)) t.s.tx(to=b'', data=byte_code) return lll_compiler + +@pytest.fixture +def assert_compile_failed(assert_tx_failed): + return assert_tx_failed From c7440524c6a91f6a4a9480a1580cb57d8f1174ba Mon Sep 17 00:00:00 2001 From: David Knott Date: Mon, 13 Nov 2017 17:18:36 -0700 Subject: [PATCH 048/162] Break out `test_parser.py` into test specific files --- tests/parser/features/test_constructor.py | 84 ++ tests/parser/features/test_packing.py | 49 ++ tests/parser/functions/test_bitwise.py | 40 + tests/parser/functions/test_block_number.py | 12 + tests/parser/functions/test_concat.py | 97 +++ tests/parser/functions/test_ec.py | 58 ++ tests/parser/functions/test_ecrecover.py | 25 + tests/parser/functions/test_extract32.py | 96 +++ tests/parser/functions/test_length.py | 17 + tests/parser/functions/test_method_id.py | 17 + tests/parser/functions/test_minmax.py | 19 + tests/parser/functions/test_raw_call.py | 84 ++ tests/parser/functions/test_rlp_list.py | 112 +++ tests/parser/functions/test_send.py | 20 + tests/parser/functions/test_sha3.py | 67 ++ tests/parser/functions/test_slice.py | 107 +++ tests/parser/globals/test_globals.py | 18 + tests/parser/integration/test_basics.py | 23 + tests/parser/types/numbers/test_num256.py | 16 + tests/test_parser.py | 911 -------------------- 20 files changed, 961 insertions(+), 911 deletions(-) create mode 100644 tests/parser/features/test_constructor.py create mode 100644 tests/parser/features/test_packing.py create mode 100644 tests/parser/functions/test_bitwise.py create mode 100644 tests/parser/functions/test_block_number.py create mode 100644 tests/parser/functions/test_concat.py create mode 100644 tests/parser/functions/test_ec.py create mode 100644 tests/parser/functions/test_ecrecover.py create mode 100644 tests/parser/functions/test_extract32.py create mode 100644 tests/parser/functions/test_length.py create mode 100644 tests/parser/functions/test_method_id.py create mode 100644 tests/parser/functions/test_minmax.py create mode 100644 tests/parser/functions/test_raw_call.py create mode 100644 tests/parser/functions/test_rlp_list.py create mode 100644 tests/parser/functions/test_send.py create mode 100644 tests/parser/functions/test_sha3.py create mode 100644 tests/parser/functions/test_slice.py create mode 100644 tests/parser/globals/test_globals.py delete mode 100644 tests/test_parser.py diff --git a/tests/parser/features/test_constructor.py b/tests/parser/features/test_constructor.py new file mode 100644 index 0000000000..63738e45bf --- /dev/null +++ b/tests/parser/features/test_constructor.py @@ -0,0 +1,84 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_init_argument_test(): + init_argument_test = """ +moose: num +def __init__(_moose: num): + self.moose = _moose + +def returnMoose() -> num: + return self.moose + """ + + c = get_contract_with_gas_estimation(init_argument_test, args=[5]) + assert c.returnMoose() == 5 + print('Passed init argument test') + + +def test_constructor_advanced_code(): + constructor_advanced_code = """ +twox: num + +def __init__(x: num): + self.twox = x * 2 + +def get_twox() -> num: + return self.twox + """ + c = get_contract_with_gas_estimation(constructor_advanced_code, args=[5]) + assert c.get_twox() == 10 + + +def test_constructor_advanced_code2(): + constructor_advanced_code2 = """ +comb: num + +def __init__(x: num[2], y: bytes <= 3, z: num): + self.comb = x[0] * 1000 + x[1] * 100 + len(y) * 10 + z + +def get_comb() -> num: + return self.comb + """ + c = get_contract_with_gas_estimation(constructor_advanced_code2, args=[[5,7], "dog", 8]) + assert c.get_comb() == 5738 + print("Passed advanced init argument tests") + + +def test_large_input_code(): + large_input_code = """ +def foo(x: num) -> num: + return 3 + """ + + c = get_contract_with_gas_estimation(large_input_code) + c.foo(1274124) + c.foo(2**120) + try: + c.foo(2**130) + success = True + except: + success = False + assert not success + + +def test_large_input_code_2(): + large_input_code_2 = """ +def __init__(x: num): + y = x + +def foo() -> num: + return 5 + """ + + c = get_contract_with_gas_estimation(large_input_code_2, args=[17], sender=t.k0, value=0) + try: + c = get_contract_with_gas_estimation(large_input_code_2, args=[2**130], sender=t.k0, value=0) + success = True + except: + success = False + assert not success + + print('Passed invalid input tests') diff --git a/tests/parser/features/test_packing.py b/tests/parser/features/test_packing.py new file mode 100644 index 0000000000..a365a8942b --- /dev/null +++ b/tests/parser/features/test_packing.py @@ -0,0 +1,49 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_packing_test(): + packing_test = """ +x: num +y: num[5] +z: {foo: num[3], bar: {a: num, b: num}[2]} +a: num + +def foo() -> num: + self.x = 1 + self.y[0] = 2 + self.y[4] = 4 + self.z.foo[0] = 8 + self.z.foo[2] = 16 + self.z.bar[0].a = 32 + self.z.bar[0].b = 64 + self.z.bar[1].a = 128 + self.z.bar[1].b = 256 + self.a = 512 + return self.x + self.y[0] + self.y[4] + self.z.foo[0] + self.z.foo[2] + \ + self.z.bar[0].a + self.z.bar[0].b + self.z.bar[1].a + self.z.bar[1].b + self.a + +def fop() -> num: + _x: num + _y: num[5] + _z: {foo: num[3], bar: {a: num, b: num}[2]} + _a: num + _x = 1 + _y[0] = 2 + _y[4] = 4 + _z.foo[0] = 8 + _z.foo[2] = 16 + _z.bar[0].a = 32 + _z.bar[0].b = 64 + _z.bar[1].a = 128 + _z.bar[1].b = 256 + _a = 512 + return _x + _y[0] + _y[4] + _z.foo[0] + _z.foo[2] + \ + _z.bar[0].a + _z.bar[0].b + _z.bar[1].a + _z.bar[1].b + _a + """ + + c = get_contract_with_gas_estimation(packing_test) + assert c.foo() == 1023, c.foo() + assert c.fop() == 1023, c.fop() + print('Passed packing test') diff --git a/tests/parser/functions/test_bitwise.py b/tests/parser/functions/test_bitwise.py new file mode 100644 index 0000000000..492e21d03c --- /dev/null +++ b/tests/parser/functions/test_bitwise.py @@ -0,0 +1,40 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_test_bitwise(): + test_bitwise = """ +def _bitwise_and(x: num256, y: num256) -> num256: + return bitwise_and(x, y) + +def _bitwise_or(x: num256, y: num256) -> num256: + return bitwise_or(x, y) + +def _bitwise_xor(x: num256, y: num256) -> num256: + return bitwise_xor(x, y) + +def _bitwise_not(x: num256) -> num256: + return bitwise_not(x) + +def _shift(x: num256, y: num) -> num256: + return shift(x, y) + """ + + c = get_contract_with_gas_estimation(test_bitwise) + x = 126416208461208640982146408124 + y = 7128468721412412459 + assert c._bitwise_and(x, y) == (x & y) + assert c._bitwise_or(x, y) == (x | y) + assert c._bitwise_xor(x, y) == (x ^ y) + assert c._bitwise_not(x) == 2**256 - 1 - x + assert c._shift(x, 3) == x * 8 + assert c._shift(x, 255) == 0 + assert c._shift(y, 255) == 2**255 + assert c._shift(x, 256) == 0 + assert c._shift(x, 0) == x + assert c._shift(x, -1) == x // 2 + assert c._shift(x, -3) == x // 8 + assert c._shift(x, -256) == 0 + + print("Passed bitwise operation tests") diff --git a/tests/parser/functions/test_block_number.py b/tests/parser/functions/test_block_number.py new file mode 100644 index 0000000000..daae43ebbc --- /dev/null +++ b/tests/parser/functions/test_block_number.py @@ -0,0 +1,12 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_block_number(): + block_number_code = """ +def block_number() -> num: + return block.number +""" + c = get_contract_with_gas_estimation(block_number_code) + c.block_number() == 2 diff --git a/tests/parser/functions/test_concat.py b/tests/parser/functions/test_concat.py new file mode 100644 index 0000000000..f8d2514107 --- /dev/null +++ b/tests/parser/functions/test_concat.py @@ -0,0 +1,97 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_concat(): + test_concat = """ +def foo2(input1: bytes <= 50, input2: bytes <= 50) -> bytes <= 1000: + return concat(input1, input2) + +def foo3(input1: bytes <= 50, input2: bytes <= 50, input3: bytes <= 50) -> bytes <= 1000: + return concat(input1, input2, input3) + """ + + c = get_contract_with_gas_estimation(test_concat) + assert c.foo2(b"h", b"orse") == b"horse" + assert c.foo2(b"h", b"") == b"h" + assert c.foo2(b"", b"") == b"" + assert c.foo2(b"", b"orse") == b"orse" + assert c.foo3(b"Buffalo", b" ", b"buffalo") == b"Buffalo buffalo" + assert c.foo2(b"\x36", b"\x35" * 32) == b"\x36" + b"\x35" * 32 + assert c.foo2(b"\x36" * 48, b"\x35" * 32) == b"\x36" * 48 + b"\x35" * 32 + assert c.foo3(b"horses" * 4, b"mice" * 7, b"crows" * 10) == b"horses" * 4 + b"mice" * 7 + b"crows" * 10 + print('Passed simple concat test') + + +def test_concat2(): + test_concat2 = """ +def foo(inp: bytes <= 50) -> bytes <= 1000: + x = inp + return concat(x, inp, x, inp, x, inp, x, inp, x, inp) + """ + + c = get_contract_with_gas_estimation(test_concat2) + assert c.foo(b"horse" * 9 + b"viper") == (b"horse" * 9 + b"viper") * 10 + print('Passed second concat test') + + +def test_crazy_concat_code(): + crazy_concat_code = """ +y: bytes <= 10 + +def krazykonkat(z: bytes <= 10) -> bytes <= 25: + x = "cow" + self.y = "horse" + return concat(x, " ", self.y, " ", z) + """ + + c = get_contract_with_gas_estimation(crazy_concat_code) + + assert c.krazykonkat(b"moose") == b'cow horse moose' + + print('Passed third concat test') + + +def test_concat_bytes32(): + test_concat_bytes32 = """ +def sandwich(inp: bytes <= 100, inp2: bytes32) -> bytes <= 164: + return concat(inp2, inp, inp2) + +def fivetimes(inp: bytes32) -> bytes <= 160: + return concat(inp, inp, inp, inp, inp) + """ + + c = get_contract_with_gas_estimation(test_concat_bytes32) + assert c.sandwich(b"cow", b"\x35" * 32) == b"\x35" * 32 + b"cow" + b"\x35" * 32, c.sandwich(b"cow", b"\x35" * 32) + assert c.sandwich(b"", b"\x46" * 32) == b"\x46" * 64 + assert c.sandwich(b"\x57" * 95, b"\x57" * 32) == b"\x57" * 159 + assert c.sandwich(b"\x57" * 96, b"\x57" * 32) == b"\x57" * 160 + assert c.sandwich(b"\x57" * 97, b"\x57" * 32) == b"\x57" * 161 + assert c.fivetimes(b"mongoose" * 4) == b"mongoose" * 20 + + print("Passed concat bytes32 test") + + +def test_konkat_code(): + konkat_code = """ +ecks: bytes32 + +def foo(x: bytes32, y: bytes32) -> bytes <= 64: + selfecks = x + return concat(selfecks, y) + +def goo(x: bytes32, y: bytes32) -> bytes <= 64: + self.ecks = x + return concat(self.ecks, y) + +def hoo(x: bytes32, y: bytes32) -> bytes <= 64: + return concat(x, y) + """ + + c = get_contract_with_gas_estimation(konkat_code) + assert c.foo(b'\x35' * 32, b'\x00' * 32) == b'\x35' * 32 + b'\x00' * 32 + assert c.goo(b'\x35' * 32, b'\x00' * 32) == b'\x35' * 32 + b'\x00' * 32 + assert c.hoo(b'\x35' * 32, b'\x00' * 32) == b'\x35' * 32 + b'\x00' * 32 + + print('Passed second concat tests') diff --git a/tests/parser/functions/test_ec.py b/tests/parser/functions/test_ec.py new file mode 100644 index 0000000000..637a5bf6e4 --- /dev/null +++ b/tests/parser/functions/test_ec.py @@ -0,0 +1,58 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation, G1, G1_times_two, G1_times_three, \ + curve_order, negative_G1 + + +def test_ecadd(): + ecadder = """ +x3: num256[2] +y3: num256[2] + +def _ecadd(x: num256[2], y: num256[2]) -> num256[2]: + return ecadd(x, y) + +def _ecadd2(x: num256[2], y: num256[2]) -> num256[2]: + x2 = x + y2 = [y[0], y[1]] + return ecadd(x2, y2) + +def _ecadd3(x: num256[2], y: num256[2]) -> num256[2]: + self.x3 = x + self.y3 = [y[0], y[1]] + return ecadd(self.x3, self.y3) + + """ + c = get_contract_with_gas_estimation(ecadder) + + assert c._ecadd(G1, G1) == G1_times_two + assert c._ecadd2(G1, G1_times_two) == G1_times_three + assert c._ecadd3(G1, [0, 0]) == G1 + assert c._ecadd3(G1, negative_G1) == [0, 0] + +def test_ecmul(): + ecmuller = """ +x3: num256[2] +y3: num256 + +def _ecmul(x: num256[2], y: num256) -> num256[2]: + return ecmul(x, y) + +def _ecmul2(x: num256[2], y: num256) -> num256[2]: + x2 = x + y2 = y + return ecmul(x2, y2) + +def _ecmul3(x: num256[2], y: num256) -> num256[2]: + self.x3 = x + self.y3 = y + return ecmul(self.x3, self.y3) + +""" + c = get_contract_with_gas_estimation(ecmuller) + + assert c._ecmul(G1, 0) == [0 ,0] + assert c._ecmul(G1, 1) == G1 + assert c._ecmul(G1, 3) == G1_times_three + assert c._ecmul(G1, curve_order - 1) == negative_G1 + assert c._ecmul(G1, curve_order) == [0, 0] diff --git a/tests/parser/functions/test_ecrecover.py b/tests/parser/functions/test_ecrecover.py new file mode 100644 index 0000000000..14bd17c24d --- /dev/null +++ b/tests/parser/functions/test_ecrecover.py @@ -0,0 +1,25 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_ecrecover_test(): + ecrecover_test = """ +def test_ecrecover(h: bytes32, v:num256, r:num256, s:num256) -> address: + return ecrecover(h, v, r, s) + +def test_ecrecover2() -> address: + return ecrecover(0x3535353535353535353535353535353535353535353535353535353535353535, + as_num256(28), + as_num256(63198938615202175987747926399054383453528475999185923188997970550032613358815), + as_num256(6577251522710269046055727877571505144084475024240851440410274049870970796685)) + """ + + c = get_contract_with_gas_estimation(ecrecover_test) + h = b'\x35' * 32 + k = b'\x46' * 32 + v, r, S = u.ecsign(h, k) + assert c.test_ecrecover(h, v, r, S) == '0x' + u.encode_hex(u.privtoaddr(k)) + assert c.test_ecrecover2() == '0x' + u.encode_hex(u.privtoaddr(k)) + + print("Passed ecrecover test") diff --git a/tests/parser/functions/test_extract32.py b/tests/parser/functions/test_extract32.py new file mode 100644 index 0000000000..b2bc2b757a --- /dev/null +++ b/tests/parser/functions/test_extract32.py @@ -0,0 +1,96 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_extract32_code(): + extract32_code = """ +y: bytes <= 100 +def extrakt32(inp: bytes <= 100, index: num) -> bytes32: + return extract32(inp, index) + +def extrakt32_mem(inp: bytes <= 100, index: num) -> bytes32: + x = inp + return extract32(x, index) + +def extrakt32_storage(index: num, inp: bytes <= 100) -> bytes32: + self.y = inp + return extract32(self.y, index) + """ + + c = get_contract_with_gas_estimation(extract32_code) + test_cases = ( + (b"c" * 31, 0), + (b"c" * 32, 0), + (b"c" * 32, -1), + (b"c" * 33, 0), + (b"c" * 33, 1), + (b"c" * 33, 2), + (b"cow" * 30, 0), + (b"cow" * 30, 1), + (b"cow" * 30, 31), + (b"cow" * 30, 32), + (b"cow" * 30, 33), + (b"cow" * 30, 34), + (b"cow" * 30, 58), + (b"cow" * 30, 59), + ) + + for S, i in test_cases: + expected_result = S[i: i + 32] if 0 <= i <= len(S) - 32 else None + if expected_result is None: + try: + o = c.extrakt32(S, i) + success = True + except: + success = False + assert not success + else: + assert c.extrakt32(S, i) == expected_result + assert c.extrakt32_mem(S, i) == expected_result + assert c.extrakt32_storage(i, S) == expected_result + + print("Passed bytes32 extraction test") + + +def test_extract32_code(): + extract32_code = """ +def foo(inp: bytes <= 32) -> num: + return extract32(inp, 0, type=num128) + +def bar(inp: bytes <= 32) -> num256: + return extract32(inp, 0, type=num256) + +def baz(inp: bytes <= 32) -> bytes32: + return extract32(inp, 0, type=bytes32) + +def fop(inp: bytes <= 32) -> bytes32: + return extract32(inp, 0) + +def foq(inp: bytes <= 32) -> address: + return extract32(inp, 0, type=address) + """ + + c = get_contract_with_gas_estimation(extract32_code) + assert c.foo(b"\x00" * 30 + b"\x01\x01") == 257 + assert c.bar(b"\x00" * 30 + b"\x01\x01") == 257 + try: + c.foo(b"\x80" + b"\x00" * 30) + success = True + except: + success = False + assert not success + assert c.bar(b"\x80" + b"\x00" * 31) == 2**255 + + assert c.baz(b"crow" * 8) == b"crow" * 8 + assert c.fop(b"crow" * 8) == b"crow" * 8 + assert c.foq(b"\x00" * 12 + b"3" * 20) == "0x" + "3" * 40 + try: + c.foq(b"crow" * 8) + success = True + except: + success = False + assert not success + + print('Passed extract32 test') + diff --git a/tests/parser/functions/test_length.py b/tests/parser/functions/test_length.py new file mode 100644 index 0000000000..781dc382b1 --- /dev/null +++ b/tests/parser/functions/test_length.py @@ -0,0 +1,17 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_test_length(): + test_length = """ +y: bytes <= 10 +def foo(inp: bytes <= 10) -> num: + x = slice(inp, start=1, len=5) + self.y = slice(inp, start=2, len=4) + return len(inp) * 100 + len(x) * 10 + len(self.y) + """ + + c = get_contract_with_gas_estimation(test_length) + assert c.foo(b"badminton") == 954, c.foo(b"badminton") + print('Passed length test') diff --git a/tests/parser/functions/test_method_id.py b/tests/parser/functions/test_method_id.py new file mode 100644 index 0000000000..19a97e6a6c --- /dev/null +++ b/tests/parser/functions/test_method_id.py @@ -0,0 +1,17 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_method_id_test(): + method_id_test = """ +def double(x: num) -> num: + return x * 2 + +def returnten() -> num: + ans = raw_call(self, concat(method_id("double(int128)"), as_bytes32(5)), gas=50000, outsize=32) + return as_num128(extract32(ans, 0)) + """ + c = get_contract_with_gas_estimation(method_id_test) + assert c.returnten() == 10 + print("Passed method ID test") diff --git a/tests/parser/functions/test_minmax.py b/tests/parser/functions/test_minmax.py new file mode 100644 index 0000000000..0afd28316f --- /dev/null +++ b/tests/parser/functions/test_minmax.py @@ -0,0 +1,19 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_minmax(): + minmax_test = """ +def foo() -> decimal: + return min(3, 5) + max(10, 20) + min(200.1, 400) + max(3000, 8000.02) + min(50000.003, 70000.004) + +def goo() -> num256: + return num256_add(min(as_num256(3), as_num256(5)), max(as_num256(40), as_num256(80))) + """ + + c = get_contract_with_gas_estimation(minmax_test) + assert c.foo() == 58223.123 + assert c.goo() == 83 + + print("Passed min/max test") diff --git a/tests/parser/functions/test_raw_call.py b/tests/parser/functions/test_raw_call.py new file mode 100644 index 0000000000..d350d06a47 --- /dev/null +++ b/tests/parser/functions/test_raw_call.py @@ -0,0 +1,84 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation, get_contract + + +def test_caller_code(): + caller_code = """ +def foo() -> bytes <= 7: + return raw_call(0x0000000000000000000000000000000000000004, "moose", gas=50000, outsize=5) + +def bar() -> bytes <= 7: + return raw_call(0x0000000000000000000000000000000000000004, "moose", gas=50000, outsize=3) + +def baz() -> bytes <= 7: + return raw_call(0x0000000000000000000000000000000000000004, "moose", gas=50000, outsize=7) + """ + + c = get_contract_with_gas_estimation(caller_code) + assert c.foo() == b"moose" + assert c.bar() == b"moo" + assert c.baz() == b"moose\x00\x00" + + print('Passed raw call test') + + + +def test_multiple_levels(): + inner_code = """ +def returnten() -> num: + return 10 + """ + + c = get_contract_with_gas_estimation(inner_code) + + outer_code = """ +def create_and_call_returnten(inp: address) -> num: + x = create_with_code_of(inp) + o = extract32(raw_call(x, "\xd0\x1f\xb1\xb8", outsize=32, gas=50000), 0, type=num128) + return o + +def create_and_return_forwarder(inp: address) -> address: + return create_with_code_of(inp) + """ + + c2 = get_contract(outer_code) + assert c2.create_and_call_returnten(c.address) == 10 + expected_forwarder_code_mask = b'`.`\x0c`\x009`.`\x00\xf36`\x00`\x007a\x10\x00`\x006`\x00s\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Z\xf4\x15XWa\x10\x00`\x00\xf3'[12:] + c3 = c2.create_and_return_forwarder(c.address) + assert s.head_state.get_code(c3)[:15] == expected_forwarder_code_mask[:15] + assert s.head_state.get_code(c3)[35:] == expected_forwarder_code_mask[35:] + + print('Passed forwarder test') + # TODO: This one is special + print('Gas consumed: %d' % (s.head_state.receipts[-1].gas_used - s.head_state.receipts[-2].gas_used - s.last_tx.intrinsic_gas_used)) + + +def test_multiple_levels2(): + inner_code = """ +def returnten() -> num: + assert False + return 10 + """ + + c = get_contract_with_gas_estimation(inner_code) + + outer_code = """ +def create_and_call_returnten(inp: address) -> num: + x = create_with_code_of(inp) + o = extract32(raw_call(x, "\xd0\x1f\xb1\xb8", outsize=32, gas=50000), 0, type=num128) + return o + +def create_and_return_forwarder(inp: address) -> address: + return create_with_code_of(inp) + """ + + c2 = get_contract_with_gas_estimation(outer_code) + try: + c2.create_and_call_returnten(c.address) + success = True + except: + success = False + assert not success + + print('Passed forwarder exception test') diff --git a/tests/parser/functions/test_rlp_list.py b/tests/parser/functions/test_rlp_list.py new file mode 100644 index 0000000000..ee218240de --- /dev/null +++ b/tests/parser/functions/test_rlp_list.py @@ -0,0 +1,112 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_rlp_decoder_code(): + import rlp + rlp_decoder_code = """ +u: bytes <= 100 + +def foo() -> address: + x = RLPList('\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', [address, bytes32]) + return x[0] + +def fop() -> bytes32: + x = RLPList('\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', [address, bytes32]) + return x[1] + +def foq() -> bytes <= 100: + x = RLPList('\xc5\x83cow\x03', [bytes, num]) + return x[0] + +def fos() -> num: + x = RLPList('\xc5\x83cow\x03', [bytes, num]) + return x[1] + +def fot() -> num256: + x = RLPList('\xc5\x83cow\x03', [bytes, num256]) + return x[1] + +def qoo(inp: bytes <= 100) -> address: + x = RLPList(inp, [address, bytes32]) + return x[0] + +def qos(inp: bytes <= 100) -> num: + x = RLPList(inp, [num, num]) + return x[0] + x[1] + +def qot(inp: bytes <= 100): + x = RLPList(inp, [num, num]) + +def qov(inp: bytes <= 100): + x = RLPList(inp, [num256, num256]) + +def roo(inp: bytes <= 100) -> address: + self.u = inp + x = RLPList(self.u, [address, bytes32]) + return x[0] + +def too(inp: bytes <= 100) -> bool: + x = RLPList(inp, [bool]) + return x[0] + +def voo(inp: bytes <= 1024) -> num: + x = RLPList(inp, [num, num, bytes32, num, bytes32, bytes]) + return x[1] + """ + c = get_contract_with_gas_estimation(rlp_decoder_code) + + assert c.foo() == '0x' + '35' * 20 + assert c.fop() == b'G' * 32 + assert c.foq() == b'cow' + assert c.fos() == 3 + assert c.fot() == 3 + assert c.qoo(b'\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG') == '0x' + '35' * 20 + assert c.roo(b'\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG') == '0x' + '35' * 20 + assert c.qos(rlp.encode([3, 30])) == 33 + assert c.qos(rlp.encode([3, 2**100 - 5])) == 2**100 - 2 + assert c.voo(rlp.encode([b'', b'\x01', b'\xbds\xc31\xf5=b\xa5\xcfy]\x0f\x05\x8f}\\\xf3\xe6\xea\x9d~\r\x96\xda\xdf:+\xdb4pm\xcc', b'', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b:\xcd\x85\x9b\x84`FD\xf9\xa8'\x8ezR\xd5\xc9*\xf5W\x1f\x14\xc2\x0cd\xa0\x17\xd4Z\xde\x9d\xc2\x18_\x82B\xc2\xaa\x82\x19P\xdd\xa2\xd0\xe9(\xcaO\xe2\xb1\x13s\x05yS\xc3q\xdb\x1eB\xe2g\xaa'\xba"])) == 1 + try: + c.qot(rlp.encode([7, 2**160])) + success = True + except: + success = False + assert not success + c.qov(rlp.encode([7, 2**160])) + try: + c.qov(rlp.encode([2**160])) + success = True + except: + success = False + assert not success + try: + c.qov(rlp.encode([b'\x03', b'\x00\x01'])) + success = True + except: + success = False + assert not success + c.qov(rlp.encode([b'\x03', b'\x01'])) + c.qov(rlp.encode([b'\x03', b''])) + try: + c.qov(rlp.encode([b'\x03', b'\x00'])) + success = True + except: + success = False + assert not success + assert c.too(rlp.encode([b'\x01'])) is True + assert c.too(rlp.encode([b''])) is False + try: + c.too(rlp.encode([b'\x02'])) + success = True + except: + success = False + assert not success + try: + c.too(rlp.encode([b'\x00'])) + success = True + except: + success = False + assert not success + + print('Passed RLP decoder tests') diff --git a/tests/parser/functions/test_send.py b/tests/parser/functions/test_send.py new file mode 100644 index 0000000000..821028dacc --- /dev/null +++ b/tests/parser/functions/test_send.py @@ -0,0 +1,20 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_send(): + send_test = """ + +def foo(): + send(msg.sender, self.balance+1) + +def fop(): + send(msg.sender, 10) + """ + c = s.contract(send_test, language='viper', value=10) + with pytest.raises(t.TransactionFailed): + c.foo() + c.fop() + with pytest.raises(t.TransactionFailed): + c.fop() diff --git a/tests/parser/functions/test_sha3.py b/tests/parser/functions/test_sha3.py new file mode 100644 index 0000000000..d461e61ec1 --- /dev/null +++ b/tests/parser/functions/test_sha3.py @@ -0,0 +1,67 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_hash_code(): + hash_code = """ +def foo(inp: bytes <= 100) -> bytes32: + return sha3(inp) + +def bar() -> bytes32: + return sha3("inp") + """ + + c = get_contract_with_gas_estimation(hash_code) + for inp in (b"", b"cow", b"s" * 31, b"\xff" * 32, b"\n" * 33, b"g" * 64, b"h" * 65): + assert c.foo(inp) == u.sha3(inp) + + assert c.bar() == u.sha3("inp") + + +def test_hash_code2(): + hash_code2 = """ +def foo(inp: bytes <= 100) -> bool: + return sha3(inp) == sha3("badminton") + """ + c = get_contract_with_gas_estimation(hash_code2) + assert c.foo(b"badminto") is False + assert c.foo(b"badminton") is True + + +def test_hash_code3(): + hash_code3 = """ +test: bytes <= 100 +def set_test(inp: bytes <= 100): + self.test = inp + +def tryy(inp: bytes <= 100) -> bool: + return sha3(inp) == sha3(self.test) + +def trymem(inp: bytes <= 100) -> bool: + x = self.test + return sha3(inp) == sha3(x) + +def try32(inp: bytes32) -> bool: + return sha3(inp) == sha3(self.test) + """ + c = get_contract_with_gas_estimation(hash_code3) + c.set_test(b"") + assert c.tryy(b"") is True + assert c.trymem(b"") is True + assert c.tryy(b"cow") is False + c.set_test(b"cow") + assert c.tryy(b"") is False + assert c.tryy(b"cow") is True + c.set_test(b"\x35" * 32) + assert c.tryy(b"\x35" * 32) is True + assert c.trymem(b"\x35" * 32) is True + assert c.try32(b"\x35" * 32) is True + assert c.tryy(b"\x35" * 33) is False + c.set_test(b"\x35" * 33) + assert c.tryy(b"\x35" * 32) is False + assert c.trymem(b"\x35" * 32) is False + assert c.try32(b"\x35" * 32) is False + assert c.tryy(b"\x35" * 33) is True + + print("Passed SHA3 hash test") diff --git a/tests/parser/functions/test_slice.py b/tests/parser/functions/test_slice.py new file mode 100644 index 0000000000..c348589ec9 --- /dev/null +++ b/tests/parser/functions/test_slice.py @@ -0,0 +1,107 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + + +def test_test_slice(): + test_slice = """ +def foo(inp1: bytes <= 10) -> bytes <= 3: + x = 5 + s = slice(inp1, start=3, len=3) + y = 7 + return s + +def bar(inp1: bytes <= 10) -> num: + x = 5 + s = slice(inp1, start=3, len=3) + y = 7 + return x * y + """ + + c = get_contract_with_gas_estimation(test_slice) + x = c.foo(b"badminton") + assert x == b"min", x + + assert c.bar(b"badminton") == 35 + + print('Passed slice test') + + +def test_test_slice2(): + test_slice2 = """ +def slice_tower_test(inp1: bytes <= 50) -> bytes <= 50: + inp = inp1 + for i in range(1, 11): + inp = slice(inp, start=1, len=30 - i * 2) + return inp + """ + + c = get_contract_with_gas_estimation(test_slice2) + x = c.slice_tower_test(b"abcdefghijklmnopqrstuvwxyz1234") + assert x == b"klmnopqrst", x + + print('Passed advanced slice test') + + +def test_test_slice3(): + test_slice3 = """ +x: num +s: bytes <= 50 +y: num +def foo(inp1: bytes <= 50) -> bytes <= 50: + self.x = 5 + self.s = slice(inp1, start=3, len=3) + self.y = 7 + return self.s + +def bar(inp1: bytes <= 50) -> num: + self.x = 5 + self.s = slice(inp1, start=3, len=3) + self.y = 7 + return self.x * self.y + """ + + c = get_contract_with_gas_estimation(test_slice3) + x = c.foo(b"badminton") + assert x == b"min", x + + assert c.bar(b"badminton") == 35 + + print('Passed storage slice test') + + +def test_test_slice4(): + test_slice4 = """ +def foo(inp: bytes <= 10, start: num, len: num) -> bytes <= 10: + return slice(inp, start=start, len=len) + """ + + c = get_contract_with_gas_estimation(test_slice4) + assert c.foo(b"badminton", 3, 3) == b"min" + assert c.foo(b"badminton", 0, 9) == b"badminton" + assert c.foo(b"badminton", 1, 8) == b"adminton" + assert c.foo(b"badminton", 1, 7) == b"adminto" + assert c.foo(b"badminton", 1, 0) == b"" + assert c.foo(b"badminton", 9, 0) == b"" + try: + c.foo(b"badminton", 0, 10) + assert False + except: + pass + try: + c.foo(b"badminton", 1, 9) + assert False + except: + pass + try: + c.foo(b"badminton", 9, 1) + assert False + except: + pass + try: + c.foo(b"badminton", 10, 0) + assert False + except: + pass + + print('Passed slice edge case test') diff --git a/tests/parser/globals/test_globals.py b/tests/parser/globals/test_globals.py new file mode 100644 index 0000000000..b6a349153c --- /dev/null +++ b/tests/parser/globals/test_globals.py @@ -0,0 +1,18 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation + +def test_permanent_variables_test(): + permanent_variables_test = """ +var: {a: num, b: num} +def __init__(a: num, b: num): + self.var.a = a + self.var.b = b + +def returnMoose() -> num: + return self.var.a * 10 + self.var.b + """ + + c = get_contract_with_gas_estimation(permanent_variables_test, args=[5, 7]) + assert c.returnMoose() == 57 + print('Passed init argument and variable member test') diff --git a/tests/parser/integration/test_basics.py b/tests/parser/integration/test_basics.py index c2e0270676..1ebf8cdb6f 100644 --- a/tests/parser/integration/test_basics.py +++ b/tests/parser/integration/test_basics.py @@ -23,3 +23,26 @@ def foo(x: num) -> num: c = get_contract_with_gas_estimation(basic_code) assert c.foo(9) == 18 print('Passed basic code test') + + +def test_selfcall_code_3(): + selfcall_code_3 = """ +def _hashy2(x: bytes <= 100) -> bytes32: + return sha3(x) + +def return_hash_of_cow_x_30() -> bytes32: + return self._hashy2("cowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcow") + +def _len(x: bytes <= 100) -> num: + return len(x) + +def returnten() -> num: + return self._len("badminton!") + """ + + c = get_contract_with_gas_estimation(selfcall_code_3) + assert c.return_hash_of_cow_x_30() == u.sha3(b'cow' * 30) + assert c.returnten() == 10 + + print("Passed single variable-size argument self-call test") + diff --git a/tests/parser/types/numbers/test_num256.py b/tests/parser/types/numbers/test_num256.py index 2912cb29d9..d28741268a 100644 --- a/tests/parser/types/numbers/test_num256.py +++ b/tests/parser/types/numbers/test_num256.py @@ -141,3 +141,19 @@ def built_in_conversion(x: num256) -> num: assert_tx_failed(lambda: c._num256_to_num(-1) != -1, ValueOutOfBounds) # Make sure it can't be coherced into a negative number. assert_tx_failed(lambda: c._num256_to_num_call(2**127)) + + +def test_modmul(): + modexper = """ +def exp(base: num256, exponent: num256, modulus: num256) -> num256: + o = as_num256(1) + for i in range(256): + o = num256_mulmod(o, o, modulus) + if bitwise_and(exponent, shift(as_num256(1), 255 - i)) != as_num256(0): + o = num256_mulmod(o, base, modulus) + return o + """ + + c = get_contract_with_gas_estimation(modexper) + assert c.exp(3, 5, 100) == 43 + assert c.exp(2, 997, 997) == 2 diff --git a/tests/test_parser.py b/tests/test_parser.py deleted file mode 100644 index fe33a03ba5..0000000000 --- a/tests/test_parser.py +++ /dev/null @@ -1,911 +0,0 @@ -import pytest -from .setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract, G1, G1_times_two, G1_times_three, \ - curve_order, negative_G1 - - -def test_block_number(): - block_number_code = """ -def block_number() -> num: - return block.number -""" - c = get_contract_with_gas_estimation(block_number_code) - c.block_number() == 2 - - -def test_send(): - send_test = """ - -def foo(): - send(msg.sender, self.balance+1) - -def fop(): - send(msg.sender, 10) - """ - c = s.contract(send_test, language='viper', value=10) - with pytest.raises(t.TransactionFailed): - c.foo() - c.fop() - with pytest.raises(t.TransactionFailed): - c.fop() - - -def test_init_argument_test(): - init_argument_test = """ -moose: num -def __init__(_moose: num): - self.moose = _moose - -def returnMoose() -> num: - return self.moose - """ - - c = get_contract_with_gas_estimation(init_argument_test, args=[5]) - assert c.returnMoose() == 5 - print('Passed init argument test') - - -def test_constructor_advanced_code(): - constructor_advanced_code = """ -twox: num - -def __init__(x: num): - self.twox = x * 2 - -def get_twox() -> num: - return self.twox - """ - c = get_contract_with_gas_estimation(constructor_advanced_code, args=[5]) - assert c.get_twox() == 10 - - -def test_constructor_advanced_code2(): - constructor_advanced_code2 = """ -comb: num - -def __init__(x: num[2], y: bytes <= 3, z: num): - self.comb = x[0] * 1000 + x[1] * 100 + len(y) * 10 + z - -def get_comb() -> num: - return self.comb - """ - c = get_contract_with_gas_estimation(constructor_advanced_code2, args=[[5,7], "dog", 8]) - assert c.get_comb() == 5738 - print("Passed advanced init argument tests") - -def test_permanent_variables_test(): - permanent_variables_test = """ -var: {a: num, b: num} -def __init__(a: num, b: num): - self.var.a = a - self.var.b = b - -def returnMoose() -> num: - return self.var.a * 10 + self.var.b - """ - - c = get_contract_with_gas_estimation(permanent_variables_test, args=[5, 7]) - assert c.returnMoose() == 57 - print('Passed init argument and variable member test') - - -def test_packing_test(): - packing_test = """ -x: num -y: num[5] -z: {foo: num[3], bar: {a: num, b: num}[2]} -a: num - -def foo() -> num: - self.x = 1 - self.y[0] = 2 - self.y[4] = 4 - self.z.foo[0] = 8 - self.z.foo[2] = 16 - self.z.bar[0].a = 32 - self.z.bar[0].b = 64 - self.z.bar[1].a = 128 - self.z.bar[1].b = 256 - self.a = 512 - return self.x + self.y[0] + self.y[4] + self.z.foo[0] + self.z.foo[2] + \ - self.z.bar[0].a + self.z.bar[0].b + self.z.bar[1].a + self.z.bar[1].b + self.a - -def fop() -> num: - _x: num - _y: num[5] - _z: {foo: num[3], bar: {a: num, b: num}[2]} - _a: num - _x = 1 - _y[0] = 2 - _y[4] = 4 - _z.foo[0] = 8 - _z.foo[2] = 16 - _z.bar[0].a = 32 - _z.bar[0].b = 64 - _z.bar[1].a = 128 - _z.bar[1].b = 256 - _a = 512 - return _x + _y[0] + _y[4] + _z.foo[0] + _z.foo[2] + \ - _z.bar[0].a + _z.bar[0].b + _z.bar[1].a + _z.bar[1].b + _a - """ - - c = get_contract_with_gas_estimation(packing_test) - assert c.foo() == 1023, c.foo() - assert c.fop() == 1023, c.fop() - print('Passed packing test') - - -def test_test_slice(): - test_slice = """ -def foo(inp1: bytes <= 10) -> bytes <= 3: - x = 5 - s = slice(inp1, start=3, len=3) - y = 7 - return s - -def bar(inp1: bytes <= 10) -> num: - x = 5 - s = slice(inp1, start=3, len=3) - y = 7 - return x * y - """ - - c = get_contract_with_gas_estimation(test_slice) - x = c.foo(b"badminton") - assert x == b"min", x - - assert c.bar(b"badminton") == 35 - - print('Passed slice test') - - -def test_test_slice2(): - test_slice2 = """ -def slice_tower_test(inp1: bytes <= 50) -> bytes <= 50: - inp = inp1 - for i in range(1, 11): - inp = slice(inp, start=1, len=30 - i * 2) - return inp - """ - - c = get_contract_with_gas_estimation(test_slice2) - x = c.slice_tower_test(b"abcdefghijklmnopqrstuvwxyz1234") - assert x == b"klmnopqrst", x - - print('Passed advanced slice test') - - -def test_test_slice3(): - test_slice3 = """ -x: num -s: bytes <= 50 -y: num -def foo(inp1: bytes <= 50) -> bytes <= 50: - self.x = 5 - self.s = slice(inp1, start=3, len=3) - self.y = 7 - return self.s - -def bar(inp1: bytes <= 50) -> num: - self.x = 5 - self.s = slice(inp1, start=3, len=3) - self.y = 7 - return self.x * self.y - """ - - c = get_contract_with_gas_estimation(test_slice3) - x = c.foo(b"badminton") - assert x == b"min", x - - assert c.bar(b"badminton") == 35 - - print('Passed storage slice test') - - -def test_test_slice4(): - test_slice4 = """ -def foo(inp: bytes <= 10, start: num, len: num) -> bytes <= 10: - return slice(inp, start=start, len=len) - """ - - c = get_contract_with_gas_estimation(test_slice4) - assert c.foo(b"badminton", 3, 3) == b"min" - assert c.foo(b"badminton", 0, 9) == b"badminton" - assert c.foo(b"badminton", 1, 8) == b"adminton" - assert c.foo(b"badminton", 1, 7) == b"adminto" - assert c.foo(b"badminton", 1, 0) == b"" - assert c.foo(b"badminton", 9, 0) == b"" - try: - c.foo(b"badminton", 0, 10) - assert False - except: - pass - try: - c.foo(b"badminton", 1, 9) - assert False - except: - pass - try: - c.foo(b"badminton", 9, 1) - assert False - except: - pass - try: - c.foo(b"badminton", 10, 0) - assert False - except: - pass - - print('Passed slice edge case test') - - -def test_test_length(): - test_length = """ -y: bytes <= 10 -def foo(inp: bytes <= 10) -> num: - x = slice(inp, start=1, len=5) - self.y = slice(inp, start=2, len=4) - return len(inp) * 100 + len(x) * 10 + len(self.y) - """ - - c = get_contract_with_gas_estimation(test_length) - assert c.foo(b"badminton") == 954, c.foo(b"badminton") - print('Passed length test') - - -def test_test_concat(): - test_concat = """ -def foo2(input1: bytes <= 50, input2: bytes <= 50) -> bytes <= 1000: - return concat(input1, input2) - -def foo3(input1: bytes <= 50, input2: bytes <= 50, input3: bytes <= 50) -> bytes <= 1000: - return concat(input1, input2, input3) - """ - - c = get_contract_with_gas_estimation(test_concat) - assert c.foo2(b"h", b"orse") == b"horse" - assert c.foo2(b"h", b"") == b"h" - assert c.foo2(b"", b"") == b"" - assert c.foo2(b"", b"orse") == b"orse" - assert c.foo3(b"Buffalo", b" ", b"buffalo") == b"Buffalo buffalo" - assert c.foo2(b"\x36", b"\x35" * 32) == b"\x36" + b"\x35" * 32 - assert c.foo2(b"\x36" * 48, b"\x35" * 32) == b"\x36" * 48 + b"\x35" * 32 - assert c.foo3(b"horses" * 4, b"mice" * 7, b"crows" * 10) == b"horses" * 4 + b"mice" * 7 + b"crows" * 10 - print('Passed simple concat test') - - -def test_test_concat2(): - test_concat2 = """ -def foo(inp: bytes <= 50) -> bytes <= 1000: - x = inp - return concat(x, inp, x, inp, x, inp, x, inp, x, inp) - """ - - c = get_contract_with_gas_estimation(test_concat2) - assert c.foo(b"horse" * 9 + b"viper") == (b"horse" * 9 + b"viper") * 10 - print('Passed second concat test') - - -def test_crazy_concat_code(): - crazy_concat_code = """ -y: bytes <= 10 - -def krazykonkat(z: bytes <= 10) -> bytes <= 25: - x = "cow" - self.y = "horse" - return concat(x, " ", self.y, " ", z) - """ - - c = get_contract_with_gas_estimation(crazy_concat_code) - - assert c.krazykonkat(b"moose") == b'cow horse moose' - - print('Passed third concat test') - - -def test_hash_code(): - hash_code = """ -def foo(inp: bytes <= 100) -> bytes32: - return sha3(inp) - -def bar() -> bytes32: - return sha3("inp") - """ - - c = get_contract_with_gas_estimation(hash_code) - for inp in (b"", b"cow", b"s" * 31, b"\xff" * 32, b"\n" * 33, b"g" * 64, b"h" * 65): - assert c.foo(inp) == u.sha3(inp) - - assert c.bar() == u.sha3("inp") - - -def test_hash_code2(): - hash_code2 = """ -def foo(inp: bytes <= 100) -> bool: - return sha3(inp) == sha3("badminton") - """ - c = get_contract_with_gas_estimation(hash_code2) - assert c.foo(b"badminto") is False - assert c.foo(b"badminton") is True - - -def test_hash_code3(): - hash_code3 = """ -test: bytes <= 100 -def set_test(inp: bytes <= 100): - self.test = inp - -def tryy(inp: bytes <= 100) -> bool: - return sha3(inp) == sha3(self.test) - -def trymem(inp: bytes <= 100) -> bool: - x = self.test - return sha3(inp) == sha3(x) - -def try32(inp: bytes32) -> bool: - return sha3(inp) == sha3(self.test) - """ - c = get_contract_with_gas_estimation(hash_code3) - c.set_test(b"") - assert c.tryy(b"") is True - assert c.trymem(b"") is True - assert c.tryy(b"cow") is False - c.set_test(b"cow") - assert c.tryy(b"") is False - assert c.tryy(b"cow") is True - c.set_test(b"\x35" * 32) - assert c.tryy(b"\x35" * 32) is True - assert c.trymem(b"\x35" * 32) is True - assert c.try32(b"\x35" * 32) is True - assert c.tryy(b"\x35" * 33) is False - c.set_test(b"\x35" * 33) - assert c.tryy(b"\x35" * 32) is False - assert c.trymem(b"\x35" * 32) is False - assert c.try32(b"\x35" * 32) is False - assert c.tryy(b"\x35" * 33) is True - - print("Passed SHA3 hash test") - - -def test_method_id_test(): - method_id_test = """ -def double(x: num) -> num: - return x * 2 - -def returnten() -> num: - ans = raw_call(self, concat(method_id("double(int128)"), as_bytes32(5)), gas=50000, outsize=32) - return as_num128(extract32(ans, 0)) - """ - c = get_contract_with_gas_estimation(method_id_test) - assert c.returnten() == 10 - print("Passed method ID test") - - -def test_ecrecover_test(): - ecrecover_test = """ -def test_ecrecover(h: bytes32, v:num256, r:num256, s:num256) -> address: - return ecrecover(h, v, r, s) - -def test_ecrecover2() -> address: - return ecrecover(0x3535353535353535353535353535353535353535353535353535353535353535, - as_num256(28), - as_num256(63198938615202175987747926399054383453528475999185923188997970550032613358815), - as_num256(6577251522710269046055727877571505144084475024240851440410274049870970796685)) - """ - - c = get_contract_with_gas_estimation(ecrecover_test) - h = b'\x35' * 32 - k = b'\x46' * 32 - v, r, S = u.ecsign(h, k) - assert c.test_ecrecover(h, v, r, S) == '0x' + u.encode_hex(u.privtoaddr(k)) - assert c.test_ecrecover2() == '0x' + u.encode_hex(u.privtoaddr(k)) - - print("Passed ecrecover test") - - -def test_extract32_code(): - extract32_code = """ -y: bytes <= 100 -def extrakt32(inp: bytes <= 100, index: num) -> bytes32: - return extract32(inp, index) - -def extrakt32_mem(inp: bytes <= 100, index: num) -> bytes32: - x = inp - return extract32(x, index) - -def extrakt32_storage(index: num, inp: bytes <= 100) -> bytes32: - self.y = inp - return extract32(self.y, index) - """ - - c = get_contract_with_gas_estimation(extract32_code) - test_cases = ( - (b"c" * 31, 0), - (b"c" * 32, 0), - (b"c" * 32, -1), - (b"c" * 33, 0), - (b"c" * 33, 1), - (b"c" * 33, 2), - (b"cow" * 30, 0), - (b"cow" * 30, 1), - (b"cow" * 30, 31), - (b"cow" * 30, 32), - (b"cow" * 30, 33), - (b"cow" * 30, 34), - (b"cow" * 30, 58), - (b"cow" * 30, 59), - ) - - for S, i in test_cases: - expected_result = S[i: i + 32] if 0 <= i <= len(S) - 32 else None - if expected_result is None: - try: - o = c.extrakt32(S, i) - success = True - except: - success = False - assert not success - else: - assert c.extrakt32(S, i) == expected_result - assert c.extrakt32_mem(S, i) == expected_result - assert c.extrakt32_storage(i, S) == expected_result - - print("Passed bytes32 extraction test") - - -def test_test_concat_bytes32(): - test_concat_bytes32 = """ -def sandwich(inp: bytes <= 100, inp2: bytes32) -> bytes <= 164: - return concat(inp2, inp, inp2) - -def fivetimes(inp: bytes32) -> bytes <= 160: - return concat(inp, inp, inp, inp, inp) - """ - - c = get_contract_with_gas_estimation(test_concat_bytes32) - assert c.sandwich(b"cow", b"\x35" * 32) == b"\x35" * 32 + b"cow" + b"\x35" * 32, c.sandwich(b"cow", b"\x35" * 32) - assert c.sandwich(b"", b"\x46" * 32) == b"\x46" * 64 - assert c.sandwich(b"\x57" * 95, b"\x57" * 32) == b"\x57" * 159 - assert c.sandwich(b"\x57" * 96, b"\x57" * 32) == b"\x57" * 160 - assert c.sandwich(b"\x57" * 97, b"\x57" * 32) == b"\x57" * 161 - assert c.fivetimes(b"mongoose" * 4) == b"mongoose" * 20 - - print("Passed concat bytes32 test") - - -def test_caller_code(): - caller_code = """ -def foo() -> bytes <= 7: - return raw_call(0x0000000000000000000000000000000000000004, "moose", gas=50000, outsize=5) - -def bar() -> bytes <= 7: - return raw_call(0x0000000000000000000000000000000000000004, "moose", gas=50000, outsize=3) - -def baz() -> bytes <= 7: - return raw_call(0x0000000000000000000000000000000000000004, "moose", gas=50000, outsize=7) - """ - - c = get_contract_with_gas_estimation(caller_code) - assert c.foo() == b"moose" - assert c.bar() == b"moo" - assert c.baz() == b"moose\x00\x00" - - print('Passed raw call test') - - -def test_extract32_code(): - extract32_code = """ -def foo(inp: bytes <= 32) -> num: - return extract32(inp, 0, type=num128) - -def bar(inp: bytes <= 32) -> num256: - return extract32(inp, 0, type=num256) - -def baz(inp: bytes <= 32) -> bytes32: - return extract32(inp, 0, type=bytes32) - -def fop(inp: bytes <= 32) -> bytes32: - return extract32(inp, 0) - -def foq(inp: bytes <= 32) -> address: - return extract32(inp, 0, type=address) - """ - - c = get_contract_with_gas_estimation(extract32_code) - assert c.foo(b"\x00" * 30 + b"\x01\x01") == 257 - assert c.bar(b"\x00" * 30 + b"\x01\x01") == 257 - try: - c.foo(b"\x80" + b"\x00" * 30) - success = True - except: - success = False - assert not success - assert c.bar(b"\x80" + b"\x00" * 31) == 2**255 - - assert c.baz(b"crow" * 8) == b"crow" * 8 - assert c.fop(b"crow" * 8) == b"crow" * 8 - assert c.foq(b"\x00" * 12 + b"3" * 20) == "0x" + "3" * 40 - try: - c.foq(b"crow" * 8) - success = True - except: - success = False - assert not success - - print('Passed extract32 test') - - -def test_rlp_decoder_code(): - import rlp - rlp_decoder_code = """ -u: bytes <= 100 - -def foo() -> address: - x = RLPList('\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', [address, bytes32]) - return x[0] - -def fop() -> bytes32: - x = RLPList('\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', [address, bytes32]) - return x[1] - -def foq() -> bytes <= 100: - x = RLPList('\xc5\x83cow\x03', [bytes, num]) - return x[0] - -def fos() -> num: - x = RLPList('\xc5\x83cow\x03', [bytes, num]) - return x[1] - -def fot() -> num256: - x = RLPList('\xc5\x83cow\x03', [bytes, num256]) - return x[1] - -def qoo(inp: bytes <= 100) -> address: - x = RLPList(inp, [address, bytes32]) - return x[0] - -def qos(inp: bytes <= 100) -> num: - x = RLPList(inp, [num, num]) - return x[0] + x[1] - -def qot(inp: bytes <= 100): - x = RLPList(inp, [num, num]) - -def qov(inp: bytes <= 100): - x = RLPList(inp, [num256, num256]) - -def roo(inp: bytes <= 100) -> address: - self.u = inp - x = RLPList(self.u, [address, bytes32]) - return x[0] - -def too(inp: bytes <= 100) -> bool: - x = RLPList(inp, [bool]) - return x[0] - -def voo(inp: bytes <= 1024) -> num: - x = RLPList(inp, [num, num, bytes32, num, bytes32, bytes]) - return x[1] - """ - c = get_contract_with_gas_estimation(rlp_decoder_code) - - assert c.foo() == '0x' + '35' * 20 - assert c.fop() == b'G' * 32 - assert c.foq() == b'cow' - assert c.fos() == 3 - assert c.fot() == 3 - assert c.qoo(b'\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG') == '0x' + '35' * 20 - assert c.roo(b'\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG') == '0x' + '35' * 20 - assert c.qos(rlp.encode([3, 30])) == 33 - assert c.qos(rlp.encode([3, 2**100 - 5])) == 2**100 - 2 - assert c.voo(rlp.encode([b'', b'\x01', b'\xbds\xc31\xf5=b\xa5\xcfy]\x0f\x05\x8f}\\\xf3\xe6\xea\x9d~\r\x96\xda\xdf:+\xdb4pm\xcc', b'', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b:\xcd\x85\x9b\x84`FD\xf9\xa8'\x8ezR\xd5\xc9*\xf5W\x1f\x14\xc2\x0cd\xa0\x17\xd4Z\xde\x9d\xc2\x18_\x82B\xc2\xaa\x82\x19P\xdd\xa2\xd0\xe9(\xcaO\xe2\xb1\x13s\x05yS\xc3q\xdb\x1eB\xe2g\xaa'\xba"])) == 1 - try: - c.qot(rlp.encode([7, 2**160])) - success = True - except: - success = False - assert not success - c.qov(rlp.encode([7, 2**160])) - try: - c.qov(rlp.encode([2**160])) - success = True - except: - success = False - assert not success - try: - c.qov(rlp.encode([b'\x03', b'\x00\x01'])) - success = True - except: - success = False - assert not success - c.qov(rlp.encode([b'\x03', b'\x01'])) - c.qov(rlp.encode([b'\x03', b''])) - try: - c.qov(rlp.encode([b'\x03', b'\x00'])) - success = True - except: - success = False - assert not success - assert c.too(rlp.encode([b'\x01'])) is True - assert c.too(rlp.encode([b''])) is False - try: - c.too(rlp.encode([b'\x02'])) - success = True - except: - success = False - assert not success - try: - c.too(rlp.encode([b'\x00'])) - success = True - except: - success = False - assert not success - - print('Passed RLP decoder tests') - - -def test_konkat_code(): - konkat_code = """ -ecks: bytes32 - -def foo(x: bytes32, y: bytes32) -> bytes <= 64: - selfecks = x - return concat(selfecks, y) - -def goo(x: bytes32, y: bytes32) -> bytes <= 64: - self.ecks = x - return concat(self.ecks, y) - -def hoo(x: bytes32, y: bytes32) -> bytes <= 64: - return concat(x, y) - """ - - c = get_contract_with_gas_estimation(konkat_code) - assert c.foo(b'\x35' * 32, b'\x00' * 32) == b'\x35' * 32 + b'\x00' * 32 - assert c.goo(b'\x35' * 32, b'\x00' * 32) == b'\x35' * 32 + b'\x00' * 32 - assert c.hoo(b'\x35' * 32, b'\x00' * 32) == b'\x35' * 32 + b'\x00' * 32 - - print('Passed second concat tests') - - -def test_large_input_code(): - large_input_code = """ -def foo(x: num) -> num: - return 3 - """ - - c = get_contract_with_gas_estimation(large_input_code) - c.foo(1274124) - c.foo(2**120) - try: - c.foo(2**130) - success = True - except: - success = False - assert not success - - -def test_large_input_code_2(): - large_input_code_2 = """ -def __init__(x: num): - y = x - -def foo() -> num: - return 5 - """ - - c = get_contract_with_gas_estimation(large_input_code_2, args=[17], sender=t.k0, value=0) - try: - c = get_contract_with_gas_estimation(large_input_code_2, args=[2**130], sender=t.k0, value=0) - success = True - except: - success = False - assert not success - - print('Passed invalid input tests') - - - - - -def test_test_bitwise(): - test_bitwise = """ -def _bitwise_and(x: num256, y: num256) -> num256: - return bitwise_and(x, y) - -def _bitwise_or(x: num256, y: num256) -> num256: - return bitwise_or(x, y) - -def _bitwise_xor(x: num256, y: num256) -> num256: - return bitwise_xor(x, y) - -def _bitwise_not(x: num256) -> num256: - return bitwise_not(x) - -def _shift(x: num256, y: num) -> num256: - return shift(x, y) - """ - - c = get_contract_with_gas_estimation(test_bitwise) - x = 126416208461208640982146408124 - y = 7128468721412412459 - assert c._bitwise_and(x, y) == (x & y) - assert c._bitwise_or(x, y) == (x | y) - assert c._bitwise_xor(x, y) == (x ^ y) - assert c._bitwise_not(x) == 2**256 - 1 - x - assert c._shift(x, 3) == x * 8 - assert c._shift(x, 255) == 0 - assert c._shift(y, 255) == 2**255 - assert c._shift(x, 256) == 0 - assert c._shift(x, 0) == x - assert c._shift(x, -1) == x // 2 - assert c._shift(x, -3) == x // 8 - assert c._shift(x, -256) == 0 - - print("Passed bitwise operation tests") - - -def test_selfcall_code_3(): - selfcall_code_3 = """ -def _hashy2(x: bytes <= 100) -> bytes32: - return sha3(x) - -def return_hash_of_cow_x_30() -> bytes32: - return self._hashy2("cowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcow") - -def _len(x: bytes <= 100) -> num: - return len(x) - -def returnten() -> num: - return self._len("badminton!") - """ - - c = get_contract_with_gas_estimation(selfcall_code_3) - assert c.return_hash_of_cow_x_30() == u.sha3(b'cow' * 30) - assert c.returnten() == 10 - - print("Passed single variable-size argument self-call test") - - -def test_multiple_levels(): - inner_code = """ -def returnten() -> num: - return 10 - """ - - c = get_contract_with_gas_estimation(inner_code) - - outer_code = """ -def create_and_call_returnten(inp: address) -> num: - x = create_with_code_of(inp) - o = extract32(raw_call(x, "\xd0\x1f\xb1\xb8", outsize=32, gas=50000), 0, type=num128) - return o - -def create_and_return_forwarder(inp: address) -> address: - return create_with_code_of(inp) - """ - - c2 = get_contract(outer_code) - assert c2.create_and_call_returnten(c.address) == 10 - expected_forwarder_code_mask = b'`.`\x0c`\x009`.`\x00\xf36`\x00`\x007a\x10\x00`\x006`\x00s\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Z\xf4\x15XWa\x10\x00`\x00\xf3'[12:] - c3 = c2.create_and_return_forwarder(c.address) - assert s.head_state.get_code(c3)[:15] == expected_forwarder_code_mask[:15] - assert s.head_state.get_code(c3)[35:] == expected_forwarder_code_mask[35:] - - print('Passed forwarder test') - # TODO: This one is special - print('Gas consumed: %d' % (s.head_state.receipts[-1].gas_used - s.head_state.receipts[-2].gas_used - s.last_tx.intrinsic_gas_used)) - - -def test_multiple_levels2(): - inner_code = """ -def returnten() -> num: - assert False - return 10 - """ - - c = get_contract_with_gas_estimation(inner_code) - - outer_code = """ -def create_and_call_returnten(inp: address) -> num: - x = create_with_code_of(inp) - o = extract32(raw_call(x, "\xd0\x1f\xb1\xb8", outsize=32, gas=50000), 0, type=num128) - return o - -def create_and_return_forwarder(inp: address) -> address: - return create_with_code_of(inp) - """ - - c2 = get_contract_with_gas_estimation(outer_code) - try: - c2.create_and_call_returnten(c.address) - success = True - except: - success = False - assert not success - - print('Passed forwarder exception test') - - -def test_minmax(): - minmax_test = """ -def foo() -> decimal: - return min(3, 5) + max(10, 20) + min(200.1, 400) + max(3000, 8000.02) + min(50000.003, 70000.004) - -def goo() -> num256: - return num256_add(min(as_num256(3), as_num256(5)), max(as_num256(40), as_num256(80))) - """ - - c = get_contract_with_gas_estimation(minmax_test) - assert c.foo() == 58223.123 - assert c.goo() == 83 - - print("Passed min/max test") - -def test_ecadd(): - ecadder = """ -x3: num256[2] -y3: num256[2] - -def _ecadd(x: num256[2], y: num256[2]) -> num256[2]: - return ecadd(x, y) - -def _ecadd2(x: num256[2], y: num256[2]) -> num256[2]: - x2 = x - y2 = [y[0], y[1]] - return ecadd(x2, y2) - -def _ecadd3(x: num256[2], y: num256[2]) -> num256[2]: - self.x3 = x - self.y3 = [y[0], y[1]] - return ecadd(self.x3, self.y3) - - """ - c = get_contract_with_gas_estimation(ecadder) - - assert c._ecadd(G1, G1) == G1_times_two - assert c._ecadd2(G1, G1_times_two) == G1_times_three - assert c._ecadd3(G1, [0, 0]) == G1 - assert c._ecadd3(G1, negative_G1) == [0, 0] - -def test_ecmul(): - ecmuller = """ -x3: num256[2] -y3: num256 - -def _ecmul(x: num256[2], y: num256) -> num256[2]: - return ecmul(x, y) - -def _ecmul2(x: num256[2], y: num256) -> num256[2]: - x2 = x - y2 = y - return ecmul(x2, y2) - -def _ecmul3(x: num256[2], y: num256) -> num256[2]: - self.x3 = x - self.y3 = y - return ecmul(self.x3, self.y3) - -""" - c = get_contract_with_gas_estimation(ecmuller) - - assert c._ecmul(G1, 0) == [0 ,0] - assert c._ecmul(G1, 1) == G1 - assert c._ecmul(G1, 3) == G1_times_three - assert c._ecmul(G1, curve_order - 1) == negative_G1 - assert c._ecmul(G1, curve_order) == [0, 0] - -def test_modmul(): - modexper = """ -def exp(base: num256, exponent: num256, modulus: num256) -> num256: - o = as_num256(1) - for i in range(256): - o = num256_mulmod(o, o, modulus) - if bitwise_and(exponent, shift(as_num256(1), 255 - i)) != as_num256(0): - o = num256_mulmod(o, base, modulus) - return o - """ - - c = get_contract_with_gas_estimation(modexper) - assert c.exp(3, 5, 100) == 43 - assert c.exp(2, 997, 997) == 2 From e3e6019cb9e6be3d87f0f045f0cfab3d1e45b2d1 Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 14 Nov 2017 07:44:57 -0700 Subject: [PATCH 049/162] Change to assert_tx_failed --- tests/conftest.py | 15 +++++- .../auctions/test_simple_open_auction.py | 9 ---- .../features/iteration/test_for_in_list.py | 10 ++-- .../features/iteration/test_range_in.py | 6 +-- tests/parser/features/test_logging.py | 5 +- tests/parser/functions/test_rlp_list.py | 47 ++++--------------- tests/parser/functions/test_send.py | 8 ++-- 7 files changed, 33 insertions(+), 67 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 334a728b5e..357a5e040d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,6 @@ compile_lll, optimizer ) -from tests.setup_transaction_tests import assert_tx_failed @pytest.fixture def bytes_helper(): @@ -29,5 +28,17 @@ def lll_compiler(lll): return lll_compiler @pytest.fixture -def assert_compile_failed(assert_tx_failed): +def assert_tx_failed(): + def assert_tx_failed(function_to_test, exception = tester.TransactionFailed): + initial_state = tester.s.snapshot() + with pytest.raises(exception): + function_to_test() + tester.s.revert(initial_state) return assert_tx_failed + +@pytest.fixture +def assert_compile_failed(get_contract_from_lll): + def assert_compile_failed(function_to_test, exception = tester.TransactionFailed): + with pytest.raises(exception): + function_to_test() + return assert_compile_failed diff --git a/tests/examples/auctions/test_simple_open_auction.py b/tests/examples/auctions/test_simple_open_auction.py index e57b3adc47..d5922a71e3 100644 --- a/tests/examples/auctions/test_simple_open_auction.py +++ b/tests/examples/auctions/test_simple_open_auction.py @@ -14,15 +14,6 @@ def auction_tester(): tester.c = tester.s.contract(contract_code, language='viper', args=[tester.accounts[0], FIVE_DAYS]) return tester -@pytest.fixture -def assert_tx_failed(): - def assert_tx_failed(function_to_test, exception = tester.TransactionFailed): - initial_state = tester.s.snapshot() - with pytest.raises(exception): - function_to_test() - tester.s.revert(initial_state) - return assert_tx_failed - def test_initial_state(auction_tester): # Check beneficiary is correct diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py index 878ea7af42..657c48bd37 100644 --- a/tests/parser/features/iteration/test_for_in_list.py +++ b/tests/parser/features/iteration/test_for_in_list.py @@ -131,7 +131,7 @@ def i_return(break_count: num) -> decimal: assert c.ret(0) == c.i_return(0) == 0.0001 -def test_altering_list_within_for_loop(): +def test_altering_list_within_for_loop(assert_compile_failed): code = """ def data() -> num: s = [1, 2, 3, 4, 5, 6] @@ -144,11 +144,10 @@ def data() -> num: return -1 """ - with pytest.raises(StructureException): - get_contract(code) + assert_compile_failed(lambda: get_contract(code), StructureException) -def test_altering_list_within_for_loop_storage(): +def test_altering_list_within_for_loop_storage(assert_compile_failed): code = """ s: num[6] @@ -165,5 +164,4 @@ def data() -> num: return -1 """ - with pytest.raises(StructureException): - get_contract(code) + assert_compile_failed(lambda: get_contract(code), StructureException) diff --git a/tests/parser/features/iteration/test_range_in.py b/tests/parser/features/iteration/test_range_in.py index b0a7b4e8a1..0417ae29c1 100644 --- a/tests/parser/features/iteration/test_range_in.py +++ b/tests/parser/features/iteration/test_range_in.py @@ -62,7 +62,7 @@ def in_test(x: num) -> bool: assert c.in_test(7) is True -def test_mixed_in_list(): +def test_mixed_in_list(assert_compile_failed): code = """ def testin() -> bool: s = [1, 2, 3, 4] @@ -70,5 +70,5 @@ def testin() -> bool: return True return False """ - with pytest.raises(TypeMismatchException): - c = get_contract(code) + + assert_compile_failed(lambda: get_contract(code), TypeMismatchException) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index 1ed0875020..c7832178c1 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -98,7 +98,7 @@ def bar(): assert c.translator.decode_event(logs.topics, logs.data) == {'_event_type': b'MyLog', 'arg1': 1, 'arg2': '0x' + c.address.hex()} -def test_event_logging_cannot_have_more_than_three_topics(): +def test_event_logging_cannot_have_more_than_three_topics(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3), arg2: indexed(bytes <= 4), arg3: indexed(address), arg4: indexed(num)}) @@ -106,8 +106,7 @@ def foo(): log.MyLog('bar', 'home', self) """ - with pytest.raises(VariableDeclarationException): - get_contract_with_gas_estimation(loggy_code) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) def test_event_logging_with_data(): diff --git a/tests/parser/functions/test_rlp_list.py b/tests/parser/functions/test_rlp_list.py index ee218240de..facde94970 100644 --- a/tests/parser/functions/test_rlp_list.py +++ b/tests/parser/functions/test_rlp_list.py @@ -1,10 +1,9 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation + get_contract_with_gas_estimation, assert_tx_failed, rlp -def test_rlp_decoder_code(): - import rlp +def test_rlp_decoder_code(assert_tx_failed): rlp_decoder_code = """ u: bytes <= 100 @@ -67,46 +66,16 @@ def voo(inp: bytes <= 1024) -> num: assert c.qos(rlp.encode([3, 30])) == 33 assert c.qos(rlp.encode([3, 2**100 - 5])) == 2**100 - 2 assert c.voo(rlp.encode([b'', b'\x01', b'\xbds\xc31\xf5=b\xa5\xcfy]\x0f\x05\x8f}\\\xf3\xe6\xea\x9d~\r\x96\xda\xdf:+\xdb4pm\xcc', b'', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b:\xcd\x85\x9b\x84`FD\xf9\xa8'\x8ezR\xd5\xc9*\xf5W\x1f\x14\xc2\x0cd\xa0\x17\xd4Z\xde\x9d\xc2\x18_\x82B\xc2\xaa\x82\x19P\xdd\xa2\xd0\xe9(\xcaO\xe2\xb1\x13s\x05yS\xc3q\xdb\x1eB\xe2g\xaa'\xba"])) == 1 - try: - c.qot(rlp.encode([7, 2**160])) - success = True - except: - success = False - assert not success + assert_tx_failed(lambda: c.qot(rlp.encode([7, 2**160]))) c.qov(rlp.encode([7, 2**160])) - try: - c.qov(rlp.encode([2**160])) - success = True - except: - success = False - assert not success - try: - c.qov(rlp.encode([b'\x03', b'\x00\x01'])) - success = True - except: - success = False - assert not success + assert_tx_failed(lambda: c.qov(rlp.encode([2**160]))) + assert_tx_failed(lambda: c.qov(rlp.encode([b'\x03', b'\x00\x01']))) c.qov(rlp.encode([b'\x03', b'\x01'])) c.qov(rlp.encode([b'\x03', b''])) - try: - c.qov(rlp.encode([b'\x03', b'\x00'])) - success = True - except: - success = False - assert not success + assert_tx_failed(lambda: c.qov(rlp.encode([b'\x03', b'\x00']))) assert c.too(rlp.encode([b'\x01'])) is True assert c.too(rlp.encode([b''])) is False - try: - c.too(rlp.encode([b'\x02'])) - success = True - except: - success = False - assert not success - try: - c.too(rlp.encode([b'\x00'])) - success = True - except: - success = False - assert not success + assert_tx_failed(lambda: c.too(rlp.encode([b'\x02']))) + assert_tx_failed(lambda: c.too(rlp.encode([b'\x00']))) print('Passed RLP decoder tests') diff --git a/tests/parser/functions/test_send.py b/tests/parser/functions/test_send.py index 821028dacc..5df063f7ba 100644 --- a/tests/parser/functions/test_send.py +++ b/tests/parser/functions/test_send.py @@ -3,7 +3,7 @@ get_contract_with_gas_estimation -def test_send(): +def test_send(assert_tx_failed): send_test = """ def foo(): @@ -13,8 +13,6 @@ def fop(): send(msg.sender, 10) """ c = s.contract(send_test, language='viper', value=10) - with pytest.raises(t.TransactionFailed): - c.foo() + assert_tx_failed(lambda: c.foo()) c.fop() - with pytest.raises(t.TransactionFailed): - c.fop() + assert_tx_failed(lambda: c.fop()) From d8056782bf4aea97bcac0d09a0c2e682d7f1fdaf Mon Sep 17 00:00:00 2001 From: David Knott Date: Fri, 13 Oct 2017 10:48:20 -0400 Subject: [PATCH 050/162] Add staticcall opcode for constant function calls --- .../features/test_external_contract_calls.py | 29 ++++++++++++++++++- viper/opcodes.py | 1 + viper/parser/parser.py | 29 +++++++++++-------- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/tests/parser/features/test_external_contract_calls.py b/tests/parser/features/test_external_contract_calls.py index aba1f0529b..c97ed21d2d 100644 --- a/tests/parser/features/test_external_contract_calls.py +++ b/tests/parser/features/test_external_contract_calls.py @@ -75,7 +75,7 @@ def get_array(arg1: address) -> bytes <= 3: assert c2.get_array(c.address) == b'dog' -def test_external_contract_call__state_change(): +def test_external_contract_call_state_change(): contract_1 = """ lucky: public(num) @@ -101,6 +101,33 @@ def set_lucky(arg1: address, arg2: num): print('Successfully executed an external contract call state change') +def test_constant_external_contract_call_cannot_change_state(): + contract_1 = """ +lucky: public(num) + +def set_lucky(_lucky: num): + self.lucky = _lucky + """ + + lucky_number = 7 + c = get_contract(contract_1) + + contract_2 = """ +class Foo(): + def set_lucky(_lucky: num) -> num: pass + +@constant +def set_lucky(arg1: address, arg2: num): + Foo(arg1).set_lucky(arg2) + """ + c2 = get_contract(contract_2) + + assert c.get_lucky() == 0 + c2.set_lucky(c.address, lucky_number) + assert c.get_lucky() == 0 + print('Successfully executed an external contract call state change') + + def test_external_contract_can_be_changed_based_on_address(): contract_1 = """ lucky: public(num) diff --git a/viper/opcodes.py b/viper/opcodes.py index d1ed4df9ae..1b972b478d 100644 --- a/viper/opcodes.py +++ b/viper/opcodes.py @@ -68,6 +68,7 @@ 'INVALID': [0xfe, 0, 0, 0], 'SUICIDE': [0xff, 1, 0, 5000], 'SELFDESTRUCT': [0xff, 1, 0, 25000], + 'STATICCALL': [0xfa, 6, 1, 40], } pseudo_opcodes = { diff --git a/viper/parser/parser.py b/viper/parser/parser.py index fb7718a7a2..bd46e146fb 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -461,11 +461,13 @@ def external_contract_call_stmt(stmt, context): sig = context.sigs[contract_name][method_name] contract_address = parse_expr(stmt.func.value.args[0], context) inargs, inargsize = pack_arguments(sig, [parse_expr(arg, context) for arg in stmt.args], context) - o = LLLnode.from_list(['seq', - ['assert', ['extcodesize', ['mload', contract_address]]], - ['assert', ['ne', 'address', ['mload', contract_address]]], - ['assert', ['call', ['gas'], ['mload', contract_address], 0, inargs, inargsize, 0, 0]]], - typ=None, location='memory', pos=getpos(stmt)) + sub = ['seq', ['assert', ['extcodesize', ['mload', contract_address]]], + ['assert', ['ne', 'address', ['mload', contract_address]]]] + if context.is_constant: + sub.append(['assert', ['staticcall', 'gas', ['mload', contract_address], inargs, inargsize, 0, 0]]) + else: + sub.append(['assert', ['call', 'gas', ['mload', contract_address], 0, inargs, inargsize, 0, 0]]) + o = LLLnode.from_list(sub, typ=sig.output_type, location='memory', pos=getpos(stmt)) return o @@ -487,13 +489,16 @@ def external_contract_call_expr(expr, context): returner = output_placeholder + 32 else: raise TypeMismatchException("Invalid output type: %r" % sig.output_type, expr) - o = LLLnode.from_list(['seq', - ['assert', ['extcodesize', ['mload', contract_address]]], - ['assert', ['ne', 'address', ['mload', contract_address]]], - ['assert', ['call', ['gas'], ['mload', contract_address], 0, - inargs, inargsize, - output_placeholder, get_size_of_type(sig.output_type) * 32]], - returner], typ=sig.output_type, location='memory', pos=getpos(expr)) + sub = ['seq', ['assert', ['extcodesize', ['mload', contract_address]]], + ['assert', ['ne', 'address', ['mload', contract_address]]]] + if context.is_constant: + sub.append(['assert', ['staticcall', 'gas', ['mload', contract_address], inargs, inargsize, + output_placeholder, get_size_of_type(sig.output_type) * 32]]) + else: + sub.append(['assert', ['call', 'gas', ['mload', contract_address], 0, inargs, inargsize, + output_placeholder, get_size_of_type(sig.output_type) * 32]]) + sub.extend([0, returner]) + o = LLLnode.from_list(sub, typ=sig.output_type, location='memory', pos=getpos(expr)) return o From cbf70ed33ac7613bf0d02748b4b275cbe66e3a03 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sat, 11 Nov 2017 17:52:45 -0700 Subject: [PATCH 051/162] Test constant external function calls --- .../features/test_external_contract_calls.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/parser/features/test_external_contract_calls.py b/tests/parser/features/test_external_contract_calls.py index c97ed21d2d..9cf23aadca 100644 --- a/tests/parser/features/test_external_contract_calls.py +++ b/tests/parser/features/test_external_contract_calls.py @@ -101,12 +101,13 @@ def set_lucky(arg1: address, arg2: num): print('Successfully executed an external contract call state change') -def test_constant_external_contract_call_cannot_change_state(): +def test_constant_external_contract_call_cannot_change_state(assert_tx_failed): contract_1 = """ lucky: public(num) -def set_lucky(_lucky: num): +def set_lucky(_lucky: num) -> num: self.lucky = _lucky + return _lucky """ lucky_number = 7 @@ -117,15 +118,18 @@ class Foo(): def set_lucky(_lucky: num) -> num: pass @constant -def set_lucky(arg1: address, arg2: num): +def set_lucky_expr(arg1: address, arg2: num): Foo(arg1).set_lucky(arg2) + +@constant +def set_lucky_stmt(arg1: address, arg2: num) -> num: + return Foo(arg1).set_lucky(arg2) """ c2 = get_contract(contract_2) - assert c.get_lucky() == 0 - c2.set_lucky(c.address, lucky_number) - assert c.get_lucky() == 0 - print('Successfully executed an external contract call state change') + assert_tx_failed(lambda: c2.set_lucky_expr(c.address, lucky_number)) + assert_tx_failed(lambda: c2.set_lucky_stmt(c.address, lucky_number)) + print('Successfully tested an constant external contract call attempted state change') def test_external_contract_can_be_changed_based_on_address(): From 5ffdef8628b502aa3312073e590f21d3ec8a0456 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sat, 11 Nov 2017 17:54:20 -0700 Subject: [PATCH 052/162] Remove repetitive assert_tx_failed --- tests/examples/company/test_company.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/examples/company/test_company.py b/tests/examples/company/test_company.py index 0c13672b31..cfe2f8e5e9 100644 --- a/tests/examples/company/test_company.py +++ b/tests/examples/company/test_company.py @@ -2,8 +2,9 @@ from ethereum.tools import tester as t from ethereum import utils -from viper import compiler + from tests.setup_transaction_tests import assert_tx_failed +from viper import compiler @pytest.fixture def tester(): From ff45b3fcf0b1b949fc6e2266e053686a30630f5d Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 14 Nov 2017 00:10:48 -0700 Subject: [PATCH 053/162] Create Viper VIP template --- .github/VIP_TEMPLATE.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/VIP_TEMPLATE.md diff --git a/.github/VIP_TEMPLATE.md b/.github/VIP_TEMPLATE.md new file mode 100644 index 0000000000..ed4e8500de --- /dev/null +++ b/.github/VIP_TEMPLATE.md @@ -0,0 +1,31 @@ +This is the suggested template for new VIPs. + +## Preamble + + VIP: + Title: + Author: + Type: + Status: Draft + Created: + Requires (*optional): + Replaces (*optional): + + +## Simple Summary +"If you can't explain it simply, you don't understand it well enough." Provide a simplified and layman-accessible explanation of the VIP. + +## Abstract +A short (description of the technical issue being addressed. + +## Motivation +The motivation is critical for VIPs that add or change Viper's functionality. It should clearly explain why the existing Viper functionality is inadequate to address the problem that the VIP solves. as well as how the VIP is in line with Viper's goals and design philosopy. + +## Specification +The technical specification should describe the syntax and semantics of any new feature. The specification should be detailed enough to allow any developer to implement the functionality + +## Backwards Compatibility +All VIPs that introduce backwards incompatibilities must include a section describing these incompatibilities and their severity. The VIP must explain how the author proposes to deal with these incompatibilities. + +## Copyright +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/) From bcb35274b224550a5fef8c45a8c5e55e7596a194 Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 14 Nov 2017 00:13:54 -0700 Subject: [PATCH 054/162] Add link to VIP template to the issue template --- .github/ISSUE_TEMPLATE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 415cc72e6c..4120f7a0e6 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,5 @@ +If you're proposing a new feature please follow the [VIP Template] (https://github.com/ethereum/viper/tree/master/.github/VIP_TEMPLATE.md) + * viper Version: x.x.x * pyethereum Version: x.x.x * OS: osx/linux/win From 95fb0e77472f41e14db6977917f1d0ad3b8cf8e7 Mon Sep 17 00:00:00 2001 From: David Knott Date: Mon, 13 Nov 2017 16:00:35 -0700 Subject: [PATCH 055/162] Improve rlp decoding gas estimation --- viper/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper/functions.py b/viper/functions.py index 3e59cb766e..974724d234 100644 --- a/viper/functions.py +++ b/viper/functions.py @@ -597,7 +597,7 @@ def _RLPlist(expr, args, kwargs, context): ['seq', ['with', '_sub', variable_pointer, ['pop', ['call', - 10000 + 500 * len(_format) + 10 * len(args), + 1500 + 400 * len(_format) + 10 * len(args), LLLnode.from_list(RLP_DECODER_ADDRESS, annotation='RLP decoder'), 0, ['add', '_sub', 32], From 132d3379190c68d4af4854baa82304eb8d62fdd2 Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 14 Nov 2017 09:48:40 -0700 Subject: [PATCH 056/162] Add VIP link to contributing docs --- .github/VIP_TEMPLATE.md | 4 ++-- docs/contributing.rst | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/VIP_TEMPLATE.md b/.github/VIP_TEMPLATE.md index ed4e8500de..c700ebfba6 100644 --- a/.github/VIP_TEMPLATE.md +++ b/.github/VIP_TEMPLATE.md @@ -16,10 +16,10 @@ This is the suggested template for new VIPs. "If you can't explain it simply, you don't understand it well enough." Provide a simplified and layman-accessible explanation of the VIP. ## Abstract -A short (description of the technical issue being addressed. +A short description of the technical issue being addressed. ## Motivation -The motivation is critical for VIPs that add or change Viper's functionality. It should clearly explain why the existing Viper functionality is inadequate to address the problem that the VIP solves. as well as how the VIP is in line with Viper's goals and design philosopy. +The motivation is critical for VIPs that add or change Viper's functionality. It should clearly explain why the existing Viper functionality is inadequate to address the problem that the VIP solves as well as how the VIP is in line with Viper's goals and design philosopy. ## Specification The technical specification should describe the syntax and semantics of any new feature. The specification should be detailed enough to allow any developer to implement the functionality diff --git a/docs/contributing.rst b/docs/contributing.rst index ed406acaa7..d147349090 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -17,10 +17,18 @@ In particular, we need help in the following areas: * Responding to questions from other users on `StackExchange `_ and the `Viper Gitter `_ +* Suggesting Improvements +`_ * Fixing and responding to `Viper's GitHub issues `_ +How to Suggest Improvements +=========================== + +To suggest an improvement, please create a Viper Improvement Proposal (VIP for short) +using the `VIP Template `_. + How to Report Issues ==================== From abc865191e112d8fdb6cf1f89b159f7674df11a4 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 9 Nov 2017 23:57:24 -0600 Subject: [PATCH 057/162] fix issue #453 --- docs/types.rst | 2 +- tests/parser/types/numbers/test_num.py | 42 +++++++++++++++++++++++++- viper/parser/expr.py | 2 +- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 4b4d1b4364..3e7ee92ac6 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -61,7 +61,7 @@ A signed integer (128 bit) is a type to store positive and negative integers. Values ------ -Signed integer values between -2\ :sup:`127` and (2\ :sup:`127` - 1). +Signed integer values between -2\ :sup:`127` and (2\ :sup:`127` - 1), inclusive. Operators --------- diff --git a/tests/parser/types/numbers/test_num.py b/tests/parser/types/numbers/test_num.py index bc2ab7fca9..180ea465d0 100644 --- a/tests/parser/types/numbers/test_num.py +++ b/tests/parser/types/numbers/test_num.py @@ -1,6 +1,6 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract + get_contract_with_gas_estimation, get_contract, assert_tx_failed def test_exponents_with_nums(): exp_code = """ @@ -16,3 +16,43 @@ def _num_exp(x: num, y: num) -> num: assert c._num_exp(3,3) == 27 assert c._num_exp(72,19) == 72**19 +def test_num_bound(assert_tx_failed): + num_bound_code = """ +def _num(x: num) -> num: + return x + +def _num_add(x: num, y: num) -> num: + return x + y + +def _num_sub(x: num, y: num) -> num: + return x - y + +def _num_add3(x: num, y: num, z: num) -> num: + return x + y + z + +def _num_max() -> num: + return 170141183460469231731687303715884105727 # 2**127 - 1 + +def _num_min() -> num: +# return -170141183460469231731687303715884105728 # -2**127 # FIXME: support negative literals + return -170141183460469231731687303715884105727-1 # -2**127 + """ + + c = get_contract(num_bound_code) + + t.s = s + NUM_MAX = 2**127 - 1 + NUM_MIN = -2**127 + assert c._num_add(NUM_MAX, 0) == NUM_MAX + assert c._num_sub(NUM_MIN, 0) == NUM_MIN + assert c._num_add(NUM_MAX - 1, 1) == NUM_MAX + assert c._num_sub(NUM_MIN + 1, 1) == NUM_MIN + assert_tx_failed(lambda: c._num_add(NUM_MAX, 1)) + assert_tx_failed(lambda: c._num_sub(NUM_MIN, 1)) + assert_tx_failed(lambda: c._num_add(NUM_MAX - 1, 2)) + assert_tx_failed(lambda: c._num_sub(NUM_MIN + 1, 2)) + assert c._num_max() == NUM_MAX + assert c._num_min() == NUM_MIN + + assert_tx_failed(lambda: c._num_add3(NUM_MAX, 1, -1)) + assert c._num_add3(NUM_MAX, -1, 1) == NUM_MAX diff --git a/viper/parser/expr.py b/viper/parser/expr.py index 63c8fdc5a0..ed58b08b82 100644 --- a/viper/parser/expr.py +++ b/viper/parser/expr.py @@ -78,7 +78,7 @@ def get_expr(self): def number(self): orignum = get_original_if_0x_prefixed(self.expr, self.context) if orignum is None and isinstance(self.expr.n, int): - if not (-2**127 + 1 <= self.expr.n <= 2**127 - 1): + if not (-2**127 <= self.expr.n <= 2**127 - 1): raise InvalidLiteralException("Number out of range: " + str(self.expr.n), self.expr) return LLLnode.from_list(self.expr.n, typ=BaseType('num', None), pos=getpos(self.expr)) elif isinstance(self.expr.n, float): From 5d152085dde72c9edba5d3046419d15d66596d56 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Fri, 10 Nov 2017 11:04:54 -0600 Subject: [PATCH 058/162] use clamp constants for literals as well --- viper/parser/expr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/viper/parser/expr.py b/viper/parser/expr.py index ed58b08b82..9cd004faf9 100644 --- a/viper/parser/expr.py +++ b/viper/parser/expr.py @@ -17,6 +17,7 @@ ) from viper.utils import ( MemoryPositions, + SizeLimits, bytes_to_int, string_to_bytes, DECIMAL_DIVISOR, @@ -78,7 +79,7 @@ def get_expr(self): def number(self): orignum = get_original_if_0x_prefixed(self.expr, self.context) if orignum is None and isinstance(self.expr.n, int): - if not (-2**127 <= self.expr.n <= 2**127 - 1): + if not (SizeLimits.MINNUM <= self.expr.n <= SizeLimits.MAXNUM): raise InvalidLiteralException("Number out of range: " + str(self.expr.n), self.expr) return LLLnode.from_list(self.expr.n, typ=BaseType('num', None), pos=getpos(self.expr)) elif isinstance(self.expr.n, float): From dff5ceb5e20a24fafe2e129fe1f7ba4596fd25f2 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Fri, 10 Nov 2017 14:15:12 -0600 Subject: [PATCH 059/162] fix num_min_literal parsing failure --- .../exceptions/test_invalid_literal_exception.py | 2 +- tests/parser/types/numbers/test_num.py | 3 +-- viper/parser/parser.py | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/parser/exceptions/test_invalid_literal_exception.py b/tests/parser/exceptions/test_invalid_literal_exception.py index b04632179b..965b93300a 100644 --- a/tests/parser/exceptions/test_invalid_literal_exception.py +++ b/tests/parser/exceptions/test_invalid_literal_exception.py @@ -20,7 +20,7 @@ def foo(): """, """ def foo(): - x = -170141183460469231731687303715884105728 + x = -170141183460469231731687303715884105729 # -2**127 - 1 """, """ def foo(): diff --git a/tests/parser/types/numbers/test_num.py b/tests/parser/types/numbers/test_num.py index 180ea465d0..a2586c4240 100644 --- a/tests/parser/types/numbers/test_num.py +++ b/tests/parser/types/numbers/test_num.py @@ -34,8 +34,7 @@ def _num_max() -> num: return 170141183460469231731687303715884105727 # 2**127 - 1 def _num_min() -> num: -# return -170141183460469231731687303715884105728 # -2**127 # FIXME: support negative literals - return -170141183460469231731687303715884105727-1 # -2**127 + return -170141183460469231731687303715884105728 # -2**127 """ c = get_contract(num_bound_code) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index bd46e146fb..9e8394c5f5 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -60,6 +60,7 @@ def parse(code): o = ast.parse(code) decorate_ast_with_source(o, code) + o = resolve_negative_literals(o) return o.body @@ -67,6 +68,7 @@ def parse(code): def parse_line(code): o = ast.parse(code).body[0] decorate_ast_with_source(o, code) + o = resolve_negative_literals(o) return o @@ -82,6 +84,19 @@ def visit(self, node): MyVisitor().visit(_ast) +def resolve_negative_literals(_ast): + + class RewriteUnaryOp(ast.NodeTransformer): + def visit_UnaryOp(self, node): + if isinstance(node.op, ast.USub) and isinstance(node.operand, ast.Num): + node.operand.n = 0 - node.operand.n + return node.operand + else: + return node + + return RewriteUnaryOp().visit(_ast) + + # Make a getter for a variable. This function gives an output that # contains lists of 4-tuples: # (i) the tail of the function name for the getter From 52a040f0133ffa3d012dd0951d271f6e67ac43c2 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Tue, 14 Nov 2017 18:58:54 -0600 Subject: [PATCH 060/162] fix test coverage --- tests/parser/types/numbers/test_num.py | 13 +++++++++++++ viper/functions.py | 2 -- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/parser/types/numbers/test_num.py b/tests/parser/types/numbers/test_num.py index a2586c4240..0832da2b9e 100644 --- a/tests/parser/types/numbers/test_num.py +++ b/tests/parser/types/numbers/test_num.py @@ -16,6 +16,19 @@ def _num_exp(x: num, y: num) -> num: assert c._num_exp(3,3) == 27 assert c._num_exp(72,19) == 72**19 +def test_nagative_nums(assert_tx_failed): + negative_nums_code = """ +def _negative_num() -> num: + return -1 +def _negative_exp() -> num: + return -(1+2) + """ + + c = get_contract(negative_nums_code) + t.s = s + assert c._negative_num() == -1 + assert c._negative_exp() == -3 + def test_num_bound(assert_tx_failed): num_bound_code = """ def _num(x: num) -> num: diff --git a/viper/functions.py b/viper/functions.py index 974724d234..f48061e31e 100644 --- a/viper/functions.py +++ b/viper/functions.py @@ -185,8 +185,6 @@ def as_num256(expr, args, kwargs, context): raise InvalidLiteralException("Number out of range: " + str(expr.args[0].n), expr.args[0]) return LLLnode.from_list(args[0], typ=BaseType('num256', None), pos=getpos(expr)) elif isinstance(args[0], LLLnode): - if args[0].value == "sub" and args[0].args[0].value == 0 and args[0].args[1].value > 0: - raise InvalidLiteralException("Negative numbers cannot be num256 literals") return LLLnode(value=args[0].value, args=args[0].args, typ=BaseType('num256'), pos=getpos(expr)) else: raise InvalidLiteralException("Invalid input for num256: %r" % args[0], expr) From 1e0254de56465fbc0fbf5452290f62a80f2328b8 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 9 Nov 2017 17:40:40 -0600 Subject: [PATCH 061/162] fix issue #448: compile_lll.py: buggy translation of LLL operator `sha3_32` --- viper/compile_lll.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper/compile_lll.py b/viper/compile_lll.py index f29a4c9032..b59b13b00d 100644 --- a/viper/compile_lll.py +++ b/viper/compile_lll.py @@ -204,7 +204,7 @@ def compile_to_assembly(code, withargs=None, break_dest=None, height=0): # SHA3 a single value elif code.value == 'sha3_32': o = compile_to_assembly(code.args[0], withargs, break_dest, height) - o.extend(['PUSH1', MemoryPositions.FREE_VAR_SPACE, 'MSTORE', 'PUSH1', MemoryPositions.FREE_VAR_SPACE, 'PUSH1', 32, 'SHA3']) + o.extend(['PUSH1', MemoryPositions.FREE_VAR_SPACE, 'MSTORE', 'PUSH1', 32, 'PUSH1', MemoryPositions.FREE_VAR_SPACE, 'SHA3']) return o # <= operator elif code.value == 'le': From 48301a377ca442b1fbd1419b6dd35f1beb7d1807 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Tue, 14 Nov 2017 22:45:34 -0600 Subject: [PATCH 062/162] add sha3_32 compilation test --- tests/compiler/test_sha3_32.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/compiler/test_sha3_32.py diff --git a/tests/compiler/test_sha3_32.py b/tests/compiler/test_sha3_32.py new file mode 100644 index 0000000000..2a7b1a0144 --- /dev/null +++ b/tests/compiler/test_sha3_32.py @@ -0,0 +1,9 @@ +import pytest +from viper.parser.parser_utils import LLLnode +from viper import compile_lll, optimizer + +def test_sha3_32(): + lll = ['sha3_32', 0] + evm = ['PUSH1', 0, 'PUSH1', 192, 'MSTORE', 'PUSH1', 32, 'PUSH1', 192, 'SHA3'] + assert compile_lll.compile_to_assembly(LLLnode.from_list(lll)) == evm + assert compile_lll.compile_to_assembly(optimizer.optimize(LLLnode.from_list(lll))) == evm From b905905c483fb976cc1166b39088c0b02b03d6a4 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Wed, 15 Nov 2017 16:03:05 +0200 Subject: [PATCH 063/162] Add checks preventing assigning values on defining types. --- tests/parser/exceptions/test_structure_exception.py | 10 ++++++++++ viper/parser/parser.py | 4 +++- viper/parser/stmt.py | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/parser/exceptions/test_structure_exception.py b/tests/parser/exceptions/test_structure_exception.py index 5591d98dc2..764aea7517 100644 --- a/tests/parser/exceptions/test_structure_exception.py +++ b/tests/parser/exceptions/test_structure_exception.py @@ -131,6 +131,16 @@ def foo(): """ def foo(): x = y = 3 + """, + """ +def foo() -> num: + q:num = 111 + return q + """, + """ +q:num = 111 +def foo() -> num: + return self.q """ ] diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 9e8394c5f5..ffcbcde519 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -171,7 +171,9 @@ def add_contract(code): def add_globals_and_events(_defs, _events, _getters, _globals, item): - if isinstance(item.annotation, ast.Call) and item.annotation.func.id == "__log__": + if item.value is not None: + raise StructureException('May not assign value whilst defining type', item) + elif isinstance(item.annotation, ast.Call) and item.annotation.func.id == "__log__": if _globals or len(_defs): raise StructureException("Events must all come before global declarations and function definitions", item) _events.append(item) diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index 635bcd54a8..a4715f4570 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -69,6 +69,8 @@ def parse_pass(self): return LLLnode.from_list('pass', typ=None, pos=getpos(self.stmt)) def ann_assign(self): + if self.stmt.value is not None: + raise StructureException('May not assign value whilst defining type', self.stmt) typ = parse_type(self.stmt.annotation, location='memory') varname = self.stmt.target.id pos = self.context.new_variable(varname, typ) From eeac89d08277b700fb10b5556f8feba6f8f28b3b Mon Sep 17 00:00:00 2001 From: David Knott Date: Wed, 15 Nov 2017 18:21:36 -0700 Subject: [PATCH 064/162] Fix gas estimation error --- viper/parser/parser_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/viper/parser/parser_utils.py b/viper/parser/parser_utils.py index 6ec41d87d7..74ba713278 100644 --- a/viper/parser/parser_utils.py +++ b/viper/parser/parser_utils.py @@ -81,7 +81,11 @@ def __init__(self, value, args=None, typ=None, location=None, pos=None, annotati self.gas += 15000 # Dynamic gas cost: calldatacopy elif self.value.upper() in ('CALLDATACOPY', 'CODECOPY'): - self.gas += ceil32(self.args[2].value) // 32 * 3 + if isinstance(self.args[2].value, int): + size = self.args[2].value + else: + size = self.args[2].args[-1].value + self.gas += ceil32(size) // 32 * 3 # Gas limits in call if self.value.upper() == 'CALL' and isinstance(self.args[0].value, int): self.gas += self.args[0].value From 42bca94261d4a7f30fd37a4aaf8658c804573018 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sat, 11 Nov 2017 12:25:56 -0700 Subject: [PATCH 065/162] Fix repeat LLL with <= 0 iteration --- viper/compile_lll.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/viper/compile_lll.py b/viper/compile_lll.py index f29a4c9032..ec5a3f260d 100644 --- a/viper/compile_lll.py +++ b/viper/compile_lll.py @@ -93,7 +93,9 @@ def compile_to_assembly(code, withargs=None, break_dest=None, height=0): # Repeat(memloc, start, rounds, body) elif code.value == 'repeat': o = [] - loops = num_to_bytearray(code.args[2].value) or [2] + loops = num_to_bytearray(code.args[2].value) + if not loops: + raise Exception("Number of times repeated must be a constant nonzero positive integer: %r" % loops) start, end = mksymbol(), mksymbol() o.extend(compile_to_assembly(code.args[0], withargs, break_dest, height)) o.extend(compile_to_assembly(code.args[1], withargs, break_dest, height + 1)) From 406443021531b9ea634236c333d7db016f047e02 Mon Sep 17 00:00:00 2001 From: David Knott Date: Wed, 15 Nov 2017 19:45:03 -0700 Subject: [PATCH 066/162] Add test for repat lll --- tests/compiler/LLL/test_repeat.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/compiler/LLL/test_repeat.py diff --git a/tests/compiler/LLL/test_repeat.py b/tests/compiler/LLL/test_repeat.py new file mode 100644 index 0000000000..3c483343a7 --- /dev/null +++ b/tests/compiler/LLL/test_repeat.py @@ -0,0 +1,9 @@ +import pytest + +def test_repeat(t, get_contract_from_lll, assert_compile_failed): + good_lll = ['repeat', 0, 0, 1, ['seq']] + bad_lll_1 = ['repeat', 0, 0, 0, ['seq']] + bad_lll_2 = ['repeat', 0, 0, -1, ['seq']] + get_contract_from_lll(good_lll) + assert_compile_failed(lambda: get_contract_from_lll(bad_lll_1), Exception) + assert_compile_failed(lambda: get_contract_from_lll(bad_lll_2), Exception) From 67dd4768477aca739e23bb872390b2db672db4b4 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sat, 11 Nov 2017 13:01:34 -0700 Subject: [PATCH 067/162] Add depth check to with "set" statement --- tests/compiler/LLL/test_with.py | 36 +++++++++++++++++++++++++++++++++ viper/compile_lll.py | 2 ++ 2 files changed, 38 insertions(+) create mode 100644 tests/compiler/LLL/test_with.py diff --git a/tests/compiler/LLL/test_with.py b/tests/compiler/LLL/test_with.py new file mode 100644 index 0000000000..0f6e9fea90 --- /dev/null +++ b/tests/compiler/LLL/test_with.py @@ -0,0 +1,36 @@ +import pytest + +def test_with_depth(t, get_contract_from_lll, assert_compile_failed): + _16_with_statements=['with', 'var_1', 0, + ['with', 'var_2', 0, + ['with', 'var_3', 0, + ['with', 'var_4', 0, + ['with', 'var_5', 0, + ['with', 'var_6', 0, + ['with', 'var_7', 0, + ['with', 'var_8', 0, + ['with', 'var_9', 0, + ['with', 'var_10', 0, + ['with', 'var_11', 0, + ['with', 'var_12', 0, + ['with', 'var_13', 0, + ['with', 'var_14', 0, + ['with', 'var_15', 0, ['mstore', 'var_1', 1]]]]]]]]]]]]]]]] + _17_with_statements=['with', 'var_1', 0, + ['with', 'var_2', 0, + ['with', 'var_3', 0, + ['with', 'var_4', 0, + ['with', 'var_5', 0, + ['with', 'var_6', 0, + ['with', 'var_7', 0, + ['with', 'var_8', 0, + ['with', 'var_9', 0, + ['with', 'var_10', 0, + ['with', 'var_11', 0, + ['with', 'var_12', 0, + ['with', 'var_13', 0, + ['with', 'var_14', 0, + ['with', 'var_15', 0, + ['with', 'var_16', 0, ['mstore', 'var_1', 1]]]]]]]]]]]]]]]]] + get_contract_from_lll(_16_with_statements) + assert_compile_failed(lambda: get_contract_from_lll(_17_with_statements), Exception) diff --git a/viper/compile_lll.py b/viper/compile_lll.py index ec5a3f260d..4ed039bb5c 100644 --- a/viper/compile_lll.py +++ b/viper/compile_lll.py @@ -54,6 +54,8 @@ def compile_to_assembly(code, withargs=None, break_dest=None, height=0): return ['DUP' + str(height - withargs[code.value])] # Setting variables connected to with statements elif code.value == "set": + if height - withargs[code.args[0].value] > 16: + raise Exception("With statement too deep") if len(code.args) != 2 or code.args[0].value not in withargs: raise Exception("Set expects two arguments, the first being a stack variable") return compile_to_assembly(code.args[1], withargs, break_dest, height) + \ From 503f72ea119905a6c867dba6bcec124b726b0170 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sat, 11 Nov 2017 12:18:58 -0700 Subject: [PATCH 068/162] Require function visibility declaration --- examples/auctions/simple_open_auction.v.py | 3 ++ examples/crowdfund.v.py | 4 ++ .../safe_remote_purchase.v.py | 8 +++- examples/stock/company.v.py | 10 +++++ .../ERC20_solidity_compatible/ERC20.v.py | 8 ++++ examples/tokens/vipercoin.v.py | 14 +++++-- examples/voting/ballot.v.py | 9 +++++ examples/wallet/wallet.v.py | 2 + .../exceptions/test_constancy_exception.py | 7 ++++ .../test_invalid_literal_exception.py | 17 ++++++++ .../parser/exceptions/test_invalid_payable.py | 3 ++ .../exceptions/test_invalid_type_exception.py | 2 + .../exceptions/test_structure_exception.py | 24 ++++++++++++ .../test_variable_declaration_exception.py | 16 ++++++++ .../features/decorators/test_internal.py | 1 + tests/parser/features/iteration/test_break.py | 3 ++ .../features/iteration/test_for_in_list.py | 14 +++++++ .../features/iteration/test_range_in.py | 4 ++ .../features/iteration/test_repeater.py | 6 ++- tests/parser/features/test_assignment.py | 5 +++ tests/parser/features/test_clampers.py | 1 + tests/parser/features/test_comments.py | 2 +- tests/parser/features/test_conditionals.py | 1 + tests/parser/features/test_constructor.py | 10 +++++ .../features/test_external_contract_calls.py | 29 +++++++++++++- tests/parser/features/test_gas.py | 4 +- tests/parser/features/test_internal_call.py | 24 ++++++++++++ tests/parser/features/test_logging.py | 39 +++++++++++++++++-- tests/parser/features/test_packing.py | 2 + tests/parser/functions/test_bitwise.py | 5 +++ tests/parser/functions/test_block_number.py | 1 + tests/parser/functions/test_concat.py | 9 +++++ tests/parser/functions/test_ec.py | 6 +++ tests/parser/functions/test_ecrecover.py | 2 + tests/parser/functions/test_extract32.py | 8 ++++ tests/parser/functions/test_is_contract.py | 2 + tests/parser/functions/test_length.py | 2 + tests/parser/functions/test_method_id.py | 2 + tests/parser/functions/test_minmax.py | 2 + tests/parser/functions/test_only_init_abi.py | 2 + tests/parser/functions/test_raw_call.py | 9 +++++ tests/parser/functions/test_return_tuple.py | 11 +++++- tests/parser/functions/test_rlp_list.py | 12 ++++++ tests/parser/functions/test_send.py | 3 +- tests/parser/functions/test_sha3.py | 8 ++++ tests/parser/functions/test_slice.py | 7 ++++ tests/parser/globals/test_getters.py | 3 ++ tests/parser/globals/test_globals.py | 3 ++ tests/parser/globals/test_setters.py | 18 +++++++++ tests/parser/integration/test_basics.py | 7 +++- tests/parser/integration/test_crowdfund.py | 18 +++++++++ tests/parser/integration/test_escrow.py | 6 +++ tests/parser/syntax/test_as_num256.py | 5 +++ tests/parser/syntax/test_as_wei.py | 6 +++ tests/parser/syntax/test_block.py | 21 ++++++++++ tests/parser/syntax/test_bool.py | 14 +++++++ tests/parser/syntax/test_byte_string.py | 2 + tests/parser/syntax/test_bytes.py | 13 +++++++ tests/parser/syntax/test_code_size.py | 3 ++ tests/parser/syntax/test_concat.py | 10 +++++ .../parser/syntax/test_create_with_code_of.py | 4 ++ tests/parser/syntax/test_extract32.py | 4 ++ tests/parser/syntax/test_for_range.py | 3 ++ tests/parser/syntax/test_invalids.py | 31 +++++++++++++++ tests/parser/syntax/test_len.py | 3 ++ tests/parser/syntax/test_list.py | 31 +++++++++++++++ tests/parser/syntax/test_maps.py | 27 +++++++++++++ tests/parser/syntax/test_minmax.py | 2 + tests/parser/syntax/test_nested_list.py | 8 ++++ tests/parser/syntax/test_public.py | 1 + tests/parser/syntax/test_raw_call.py | 6 +++ tests/parser/syntax/test_return_tuple.py | 1 + tests/parser/syntax/test_rlplist.py | 8 ++++ tests/parser/syntax/test_selfdestruct.py | 2 + tests/parser/syntax/test_send.py | 11 ++++++ tests/parser/syntax/test_sha3.py | 3 ++ tests/parser/syntax/test_slice.py | 6 +++ .../parser/syntax/test_timestamp_timedelta.py | 31 +++++++++++++++ tests/parser/types/numbers/test_decimals.py | 19 +++++++++ tests/parser/types/numbers/test_num.py | 1 + tests/parser/types/numbers/test_num256.py | 16 ++++++++ tests/parser/types/test_bytes.py | 17 ++++++++ tests/parser/types/test_lists.py | 19 +++++++++ tests/parser/types/test_string_literal.py | 9 +++++ tests/parser/types/value/test_wei.py | 5 +++ viper/function_signature.py | 9 ++++- viper/parser/parser.py | 2 +- 87 files changed, 752 insertions(+), 19 deletions(-) diff --git a/examples/auctions/simple_open_auction.v.py b/examples/auctions/simple_open_auction.v.py index c270ce760a..e6421db653 100644 --- a/examples/auctions/simple_open_auction.v.py +++ b/examples/auctions/simple_open_auction.v.py @@ -16,6 +16,7 @@ # Create a simple auction with `_bidding_time` # seconds bidding time on behalf of the # beneficiary address `_beneficiary`. +@public def __init__(_beneficiary: address, _bidding_time: timedelta): self.beneficiary = _beneficiary self.auction_start = block.timestamp @@ -25,6 +26,7 @@ def __init__(_beneficiary: address, _bidding_time: timedelta): # together with this transaction. # The value will only be refunded if the # auction is not won. +@public @payable def bid(): # Check if bidding period is over. @@ -40,6 +42,7 @@ def bid(): # End the auction and send the highest bid # to the beneficiary. +@public def auction_end(): # It is a good guideline to structure functions that interact # with other contracts (i.e. they call functions or send Ether) diff --git a/examples/crowdfund.v.py b/examples/crowdfund.v.py index aa2e9f1b5e..88092d00b8 100644 --- a/examples/crowdfund.v.py +++ b/examples/crowdfund.v.py @@ -7,6 +7,7 @@ timelimit: timedelta # Setup global variables +@public def __init__(_beneficiary: address, _goal: wei_value, _timelimit: timedelta): self.beneficiary = _beneficiary self.deadline = block.timestamp + _timelimit @@ -14,6 +15,7 @@ def __init__(_beneficiary: address, _goal: wei_value, _timelimit: timedelta): self.goal = _goal # Participate in this crowdfunding campaign +@public @payable def participate(): assert block.timestamp < self.deadline @@ -22,12 +24,14 @@ def participate(): self.nextFunderIndex = nfi + 1 # Enough money was raised! Send funds to the beneficiary +@public def finalize(): assert block.timestamp >= self.deadline and self.balance >= self.goal selfdestruct(self.beneficiary) # Not enough money was raised! Refund everyone (max 30 people at a time # to avoid gas limit issues) +@public def refund(): assert block.timestamp >= self.deadline and self.balance < self.goal ind = self.refundIndex diff --git a/examples/safe_remote_purchase/safe_remote_purchase.v.py b/examples/safe_remote_purchase/safe_remote_purchase.v.py index f56f7b6ffd..94fb96def0 100644 --- a/examples/safe_remote_purchase/safe_remote_purchase.v.py +++ b/examples/safe_remote_purchase/safe_remote_purchase.v.py @@ -15,25 +15,29 @@ #def unlocked() -> bool: #Is a refund possible for the seller? # return (self.balance == self.value*2) # +@public @payable def __init__(): assert (msg.value % 2) == 0 self.value = msg.value / 2 #Seller initializes contract by posting a safety deposit of 2*value of the item up for sale self.seller = msg.sender self.unlocked = true - + +@public def abort(): assert self.unlocked #Is the contract still refundable assert msg.sender == self.seller #Only seller can refund his deposit before any buyer purchases the item selfdestruct(self.seller) #Refunds seller, deletes contract +@public @payable def purchase(): assert self.unlocked #Contract still open (item still up for sale)? assert msg.value == (2*self.value) #Is the deposit of correct value? self.buyer = msg.sender self.unlocked = false - + +@public def received(): assert not self.unlocked #Is the item already purchased and pending confirmation of buyer assert msg.sender == self.buyer diff --git a/examples/stock/company.v.py b/examples/stock/company.v.py index 84591975ba..3c51b27bee 100644 --- a/examples/stock/company.v.py +++ b/examples/stock/company.v.py @@ -7,6 +7,7 @@ holdings: currency_value[address] # Setup company +@public def __init__(_company: address, _total_shares: currency_value, initial_price: num(wei / currency) ): assert _total_shares > 0 @@ -20,11 +21,13 @@ def __init__(_company: address, _total_shares: currency_value, # Company holds all the shares at first, but can sell them all self.holdings[self.company] = _total_shares +@public @constant def stock_available() -> currency_value: return self.holdings[self.company] # Give value to company and get stock in return +@public @payable def buy_stock(): # Note: full amount is given to company (no fractional shares), @@ -39,16 +42,19 @@ def buy_stock(): self.holdings[msg.sender] += buy_order # So someone can find out how much they have +@public @constant def get_holding(_stockholder: address) -> currency_value: return self.holdings[_stockholder] # The amount the company has on hand in cash +@public @constant def cash() -> wei_value: return self.balance # Give stock back to company and get my money back! +@public def sell_stock(sell_order: currency_value): assert sell_order > 0 # Otherwise, will fail at send() below # Can only sell as much stock as you own @@ -64,6 +70,7 @@ def sell_stock(sell_order: currency_value): # Transfer stock from one stockholder to another # (Assumes the receiver is given some compensation, but not enforced) +@public def transfer_stock(receiver: address, transfer_order: currency_value): assert transfer_order > 0 # AUDIT revealed this! # Can only trade as much stock as you own @@ -74,6 +81,7 @@ def transfer_stock(receiver: address, transfer_order: currency_value): self.holdings[receiver] += transfer_order # Allows the company to pay someone for services rendered +@public def pay_bill(vendor: address, amount: wei_value): # Only the company can pay people assert msg.sender == self.company @@ -84,11 +92,13 @@ def pay_bill(vendor: address, amount: wei_value): send(vendor, amount) # The amount a company has raised in the stock offering +@public @constant def debt() -> wei_value: return (self.total_shares - self.holdings[self.company]) * self.price # The balance sheet of the company +@public @constant def worth() -> wei_value: return self.cash() - self.debt() diff --git a/examples/tokens/ERC20_solidity_compatible/ERC20.v.py b/examples/tokens/ERC20_solidity_compatible/ERC20.v.py index 59776afe64..7e253944a5 100644 --- a/examples/tokens/ERC20_solidity_compatible/ERC20.v.py +++ b/examples/tokens/ERC20_solidity_compatible/ERC20.v.py @@ -16,6 +16,7 @@ allowances: (num256[address])[address] num_issued: num256 +@public @payable def deposit(): _value = as_num256(msg.value) @@ -25,6 +26,7 @@ def deposit(): # Fire deposit event as transfer from 0x0 log.Transfer(0x0000000000000000000000000000000000000000, _sender, _value) +@public def withdraw(_value : num256) -> bool: _sender = msg.sender # Make sure sufficient funds are present, op will not underflow supply @@ -36,14 +38,17 @@ def withdraw(_value : num256) -> bool: log.Transfer(_sender, 0x0000000000000000000000000000000000000000, _value) return true +@public @constant def totalSupply() -> num256: return self.num_issued +@public @constant def balanceOf(_owner : address) -> num256: return self.balances[_owner] +@public def transfer(_to : address, _value : num256) -> bool: _sender = msg.sender # Make sure sufficient funds are present implicitly through overflow protection @@ -53,6 +58,7 @@ def transfer(_to : address, _value : num256) -> bool: log.Transfer(_sender, _to, _value) return true +@public def transferFrom(_from : address, _to : address, _value : num256) -> bool: _sender = msg.sender allowance = self.allowances[_from][_sender] @@ -64,6 +70,7 @@ def transferFrom(_from : address, _to : address, _value : num256) -> bool: log.Transfer(_from, _to, _value) return true +@public def approve(_spender : address, _value : num256) -> bool: _sender = msg.sender self.allowances[_sender][_spender] = _value @@ -71,6 +78,7 @@ def approve(_spender : address, _value : num256) -> bool: log.Approval(_sender, _spender, _value) return true +@public @constant def allowance(_owner : address, _spender : address) -> num256: return self.allowances[_owner][_spender] diff --git a/examples/tokens/vipercoin.v.py b/examples/tokens/vipercoin.v.py index 1cb162441f..864f11d6da 100644 --- a/examples/tokens/vipercoin.v.py +++ b/examples/tokens/vipercoin.v.py @@ -16,22 +16,22 @@ balances: num[address] allowed: num[address][address] - +@public def __init__(_name: bytes32, _symbol: bytes32, _decimals: num, _initialSupply: num): - + self.name = _name self.symbol = _symbol self.decimals = _decimals self.totalSupply = _initialSupply * 10 ** _decimals self.balances[msg.sender] = self.totalSupply - +@public @constant def symbol() -> bytes32: return self.symbol - +@public @constant def name() -> bytes32: @@ -39,6 +39,7 @@ def name() -> bytes32: # What is the balance of a particular account? +@public @constant def balanceOf(_owner: address) -> num256: @@ -46,6 +47,7 @@ def balanceOf(_owner: address) -> num256: # Return total supply of token. +@public @constant def totalSupply() -> num256: @@ -53,6 +55,7 @@ def totalSupply() -> num256: # Send `_value` tokens to `_to` from your account +@public def transfer(_to: address, _amount: num(num256)) -> bool: if self.balances[msg.sender] >= _amount and \ @@ -68,6 +71,7 @@ def transfer(_to: address, _amount: num(num256)) -> bool: # Transfer allowed tokens from a specific account to another. +@public def transferFrom(_from: address, _to: address, _value: num(num256)) -> bool: if _value <= self.allowed[_from][msg.sender] and \ @@ -92,6 +96,7 @@ def transferFrom(_from: address, _to: address, _value: num(num256)) -> bool: # same spender. THOUGH The contract itself shouldn't enforce it, to allow # backwards compatilibilty with contracts deployed before. # +@public def approve(_spender: address, _amount: num(num256)) -> bool: self.allowed[msg.sender][_spender] = _amount @@ -101,6 +106,7 @@ def approve(_spender: address, _amount: num(num256)) -> bool: # Get the allowance an address has to spend anothers' token. +@public def allowance(_owner: address, _spender: address) -> num256: return as_num256(self.allowed[_owner][_spender]) diff --git a/examples/voting/ballot.v.py b/examples/voting/ballot.v.py index 964650c251..9068a7ce2f 100644 --- a/examples/voting/ballot.v.py +++ b/examples/voting/ballot.v.py @@ -24,17 +24,20 @@ chairperson: public(address) num_proposals: public(num) +@public @constant def delegated(addr: address) -> bool: # equivalent to self.voters[addr].delegate != 0x0000000000000000000000000000000000000000 return not not self.voters[addr].delegate +@public @constant def directly_voted(addr: address) -> bool: # not
equivalent to
== 0x0000000000000000000000000000000000000000 return self.voters[addr].voted and not self.voters[addr].delegate # Setup global variables +@public def __init__(_proposalNames: bytes32[2]): self.chairperson = msg.sender self.voter_count = 0 @@ -47,6 +50,7 @@ def __init__(_proposalNames: bytes32[2]): # Give `voter` the right to vote on this ballot. # May only be called by `chairperson`. +@public def give_right_to_vote(voter: address): # Throws if sender is not chairperson assert msg.sender == self.chairperson @@ -58,6 +62,7 @@ def give_right_to_vote(voter: address): self.voter_count += 1 # Used by `delegate`. Can be called by anyone. +@public def forward_weight(delegate_with_weight_to_forward: address): assert self.delegated(delegate_with_weight_to_forward) # Throw if there is nothing to do: @@ -91,6 +96,7 @@ def forward_weight(delegate_with_weight_to_forward: address): # to be called again. # Delegate your vote to the voter `to`. +@public def delegate(to: address): # Throws if sender has already voted assert not self.voters[msg.sender].voted @@ -107,6 +113,7 @@ def delegate(to: address): # Give your vote (including votes delegated to you) # to proposal `proposals[proposal].name`. +@public def vote(proposal: num): # can't vote twice assert not self.voters[msg.sender].voted @@ -121,6 +128,7 @@ def vote(proposal: num): # Computes the winning proposal taking all # previous votes into account. +@public @constant def winning_proposal() -> num: winning_vote_count = 0 @@ -133,6 +141,7 @@ def winning_proposal() -> num: # Calls winning_proposal() function to get the index # of the winner contained in the proposals array and then # returns the name of the winner +@public @constant def winner_name() -> bytes32: return self.proposals[self.winning_proposal()].name diff --git a/examples/wallet/wallet.v.py b/examples/wallet/wallet.v.py index 2b8e744efe..e19048c84f 100644 --- a/examples/wallet/wallet.v.py +++ b/examples/wallet/wallet.v.py @@ -8,6 +8,7 @@ # The number of transactions that have been approved seq: num +@public def __init__(_owners: address[5], _threshold: num): for i in range(5): if _owners[i]: @@ -15,6 +16,7 @@ def __init__(_owners: address[5], _threshold: num): self.threshold = _threshold # `@payable` allows functions to receive ether +@public @payable def approve(_seq: num, to: address, value: wei_value, data: bytes <= 4096, sigdata: num256[3][5]) -> bytes <= 4096: # Throws if the value sent to the contract is less than the sum of the value to be sent diff --git a/tests/parser/exceptions/test_constancy_exception.py b/tests/parser/exceptions/test_constancy_exception.py index bbd76117bc..5fd11c9c0d 100644 --- a/tests/parser/exceptions/test_constancy_exception.py +++ b/tests/parser/exceptions/test_constancy_exception.py @@ -8,16 +8,19 @@ fail_list = [ """ x: num +@public @constant def foo() -> num: self.x = 5 """, """ +@public @constant def foo() -> num: send(0x1234567890123456789012345678901234567890, 5) """, """ +@public @constant def foo() -> num: selfdestruct(0x1234567890123456789012345678901234567890) @@ -25,24 +28,28 @@ def foo() -> num: """ x: timedelta y: num +@public @constant def foo() -> num(sec): self.y = 9 return 5 """, """ +@public @constant def foo() -> num: x = raw_call(0x1234567890123456789012345678901234567890, "cow", outsize=4, gas=595757, value=9) return 5 """, """ +@public @constant def foo() -> num: x = create_with_code_of(0x1234567890123456789012345678901234567890, value=9) return 5 """, """ +@public def foo(x: num): x = 5 """ diff --git a/tests/parser/exceptions/test_invalid_literal_exception.py b/tests/parser/exceptions/test_invalid_literal_exception.py index 965b93300a..c38995b88b 100644 --- a/tests/parser/exceptions/test_invalid_literal_exception.py +++ b/tests/parser/exceptions/test_invalid_literal_exception.py @@ -7,71 +7,88 @@ fail_list = [ """ +@public def foo(): x = 0x12345678901234567890123456789012345678901 """, """ +@public def foo(): x = 0x01234567890123456789012345678901234567890 """, """ +@public def foo(): x = 0x123456789012345678901234567890123456789 """, """ +@public def foo(): x = -170141183460469231731687303715884105729 # -2**127 - 1 """, """ +@public def foo(): x = -170141183460469231731687303715884105728. """, """ b: decimal +@public def foo(): self.b = 7.5178246872145875217495129745982164981654986129846 """, """ +@public def foo(): x = "these bytes are nо gооd because the o's are from the Russian alphabet" """, """ +@public def foo(): x = "这个傻老外不懂中文" """, """ +@public def foo(): x = raw_call(0x123456789012345678901234567890123456789, "cow", outsize=4) """, """ +@public def foo(): x = create_with_code_of(0x123456789012345678901234567890123456789) """, """ +@public def foo(): x = as_wei_value(5.1824, ada) """, """ +@public def foo(): x = as_wei_value(0x05, ada) """, """ +@public def foo(): x = as_wei_value(5, vader) """, """ +@public def foo(): send(0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae, 5) """, """ +@public def foo(): x = as_num256(821649876217461872458712528745872158745214187264875632587324658732648753245328764872135671285218762145) """, """ +@public def foo(): x = as_num256(-1) """, """ +@public def foo(): x = as_num256(3.1415) """ diff --git a/tests/parser/exceptions/test_invalid_payable.py b/tests/parser/exceptions/test_invalid_payable.py index c1a7a18930..6a082b5055 100644 --- a/tests/parser/exceptions/test_invalid_payable.py +++ b/tests/parser/exceptions/test_invalid_payable.py @@ -7,6 +7,7 @@ fail_list = [ """ +@public def foo(): x = msg.value """ @@ -22,11 +23,13 @@ def test_variable_decleration_exception(bad_code): valid_list = [ """ x: num +@public @payable def foo() -> num: self.x = 5 """, """ +@public @payable def foo(): x = msg.value diff --git a/tests/parser/exceptions/test_invalid_type_exception.py b/tests/parser/exceptions/test_invalid_type_exception.py index cf8d63b66e..1140168377 100644 --- a/tests/parser/exceptions/test_invalid_type_exception.py +++ b/tests/parser/exceptions/test_invalid_type_exception.py @@ -52,10 +52,12 @@ def foo(x): pass """, """ x: num[address[bool]] +@public def foo() -> num(wei / sec): pass """, """ +@public def foo() -> {cow: num, dog: num}: return {cow: 5, dog: 7} """ diff --git a/tests/parser/exceptions/test_structure_exception.py b/tests/parser/exceptions/test_structure_exception.py index 764aea7517..079609473a 100644 --- a/tests/parser/exceptions/test_structure_exception.py +++ b/tests/parser/exceptions/test_structure_exception.py @@ -10,6 +10,7 @@ x[5] = 4 """, """ +@public def foo(): pass x: num @@ -22,26 +23,31 @@ def foo(): pass """, """ x: num[5] +@public def foo(): self.x[2:4] = 3 """, """ x: num[5] +@public def foo(): z = self.x[2:4] """, """ +@public def foo(): x: num[5] z = x[2:4] """, """ +@public def foo(): x = 5 for i in range(x): pass """, """ +@public def foo(): x = 5 y = 7 @@ -50,48 +56,58 @@ def foo(): """, """ x: num +@public @const def foo() -> num: pass """, """ x: num +@public @monkeydoodledoo def foo() -> num: pass """, """ x: num +@public @constant(123) def foo() -> num: pass """, """ foo: num[3] +@public def foo(): self.foo = [] """, """ +@public def foo(): x = sha3("moose", 3) """, """ +@public def foo(): x = raw_call(0x1234567890123456789012345678901234567890, "cow") """, """ +@public def foo(): x = raw_call(0x1234567890123456789012345678901234567890, outsize=4) """, """ +@public def foo(): x = create_with_code_of(0x1234567890123456789012345678901234567890, "cow") """, """ +@public def foo(): x = raw_call(0x1234567890123456789012345678901234567890, "cow", gas=111111, outsize=4, moose=9) """, """ +@public def foo(): x = create_with_code_of(0x1234567890123456789012345678901234567890, outsize=4) """, @@ -99,36 +115,44 @@ def foo(): x: public() """, """ +@public def foo(): raw_log([], "cow", "dog") """, """ +@public def foo(): raw_log("cow", "dog") """, """ +@public def foo(): throe """, """ +@public def foo() -> num(wei): x = 0x1234567890123456789012345678901234567890 return x.balance() """, """ +@public def foo() -> num: x = 0x1234567890123456789012345678901234567890 return x.codesize() """, """ +@public def foo(): x = ~self """, """ +@public def foo(): x = concat("") """, """ +@public def foo(): x = y = 3 """, diff --git a/tests/parser/exceptions/test_variable_declaration_exception.py b/tests/parser/exceptions/test_variable_declaration_exception.py index 687107d1aa..55f50f2059 100644 --- a/tests/parser/exceptions/test_variable_declaration_exception.py +++ b/tests/parser/exceptions/test_variable_declaration_exception.py @@ -13,68 +13,84 @@ """ x: num +@public def foo(x: num): pass """, """ +@public def foo(x: num, x: num): pass """, """ +@public def foo(num: num): pass """, """ +@public def foo(): x = 5 x: num """, """ +@public def foo(): x: num x: num """, """ +@public def foo(): x: num +@public def foo(): y: num """, """ +@public def foo(): num = 5 """, """ +@public def foo(): bork = zork """, """ x: num +@public def foo(): x = 5 """, """ b: num +@public def foo(): b = 7 """, """ x: wei_value +@public def foo(): send(0x1234567890123456789012345678901234567890, x) """, """ +@public def foo(): true = 3 """, """ +@public def foo(): self.goo() +@public def goo(): self.foo() """, """ +@public def foo(): BALANCE = 45 """, diff --git a/tests/parser/features/decorators/test_internal.py b/tests/parser/features/decorators/test_internal.py index 28a7ef6614..8fa0d39dcb 100644 --- a/tests/parser/features/decorators/test_internal.py +++ b/tests/parser/features/decorators/test_internal.py @@ -9,6 +9,7 @@ def test_internal_test(): def a() -> num: return 5 +@public def returnten() -> num: return self.a() * 2 """ diff --git a/tests/parser/features/iteration/test_break.py b/tests/parser/features/iteration/test_break.py index d6c0aa5023..e8968753fb 100644 --- a/tests/parser/features/iteration/test_break.py +++ b/tests/parser/features/iteration/test_break.py @@ -5,6 +5,7 @@ def test_break_test(): break_test = """ +@public def log(n: num) -> num: c = n * 1.0 output = 0 @@ -26,6 +27,7 @@ def log(n: num) -> num: def test_break_test_2(): break_test_2 = """ +@public def log(n: num) -> num: c = n * 1.0 output = 0 @@ -53,6 +55,7 @@ def log(n: num) -> num: def test_break_test_3(): break_test_3 = """ +@public def log(n: num) -> num: c = decimal(n) output = 0 diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py index 657c48bd37..b45a31c676 100644 --- a/tests/parser/features/iteration/test_for_in_list.py +++ b/tests/parser/features/iteration/test_for_in_list.py @@ -6,6 +6,7 @@ def test_basic_for_in_list(): code = """ +@public def data() -> num: s = [1, 2, 3, 4, 5, 6] for i in s: @@ -21,6 +22,7 @@ def data() -> num: def test_basic_for_list_liter(): code = """ +@public def data() -> num: for i in [3, 5, 7, 9]: if i > 5: @@ -37,9 +39,11 @@ def test_basic_for_list_storage(): code = """ x: num[4] +@public def set(): self.x = [3, 5, 7, 9] +@public def data() -> num: for i in self.x: if i > 5: @@ -56,6 +60,7 @@ def data() -> num: def test_basic_for_list_address(): code = """ +@public def data() -> address: addresses = [ 0x7d577a597B2742b498Cb5Cf0C26cDCD726d39E6e, @@ -79,12 +84,15 @@ def test_basic_for_list_storage_address(): code = """ addresses: address[3] +@public def set(i: num, val: address): self.addresses[i] = val +@public def ret(i: num) -> address: return self.addresses[i] +@public def iterate_return_second() -> address: count = 0 for i in self.addresses: @@ -106,12 +114,15 @@ def test_basic_for_list_storage_decimal(): code = """ readings: decimal[3] +@public def set(i: num, val: decimal): self.readings[i] = val +@public def ret(i: num) -> decimal: return self.readings[i] +@public def i_return(break_count: num) -> decimal: count = 0 for i in self.readings: @@ -133,6 +144,7 @@ def i_return(break_count: num) -> decimal: def test_altering_list_within_for_loop(assert_compile_failed): code = """ +@public def data() -> num: s = [1, 2, 3, 4, 5, 6] count = 0 @@ -151,9 +163,11 @@ def test_altering_list_within_for_loop_storage(assert_compile_failed): code = """ s: num[6] +@public def set(): self.s = [1, 2, 3, 4, 5, 6] +@public def data() -> num: count = 0 for i in self.s: diff --git a/tests/parser/features/iteration/test_range_in.py b/tests/parser/features/iteration/test_range_in.py index 0417ae29c1..4070d4f686 100644 --- a/tests/parser/features/iteration/test_range_in.py +++ b/tests/parser/features/iteration/test_range_in.py @@ -6,6 +6,7 @@ def test_basic_in_list(): code = """ +@public def testin(x: num) -> bool: y = 1 s = [1, 2, 3, 4] @@ -29,6 +30,7 @@ def test_in_storage_list(): code = """ allowed: num[10] +@public def in_test(x: num) -> bool: self.allowed = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] if x in self.allowed: @@ -47,6 +49,7 @@ def in_test(x: num) -> bool: def test_cmp_in_list(): code = """ +@public def in_test(x: num) -> bool: if x in [9, 7, 6, 5]: return True @@ -64,6 +67,7 @@ def in_test(x: num) -> bool: def test_mixed_in_list(assert_compile_failed): code = """ +@public def testin() -> bool: s = [1, 2, 3, 4] if "test" in s: diff --git a/tests/parser/features/iteration/test_repeater.py b/tests/parser/features/iteration/test_repeater.py index 78b4179063..05b779df84 100644 --- a/tests/parser/features/iteration/test_repeater.py +++ b/tests/parser/features/iteration/test_repeater.py @@ -5,6 +5,7 @@ def test_basic_repeater(): basic_repeater = """ +@public def repeat(z: num) -> num: x = 0 for i in range(6): @@ -18,7 +19,7 @@ def repeat(z: num) -> num: def test_digit_reverser(): digit_reverser = """ - +@public def reverse_digits(x: num) -> num: dig: num[6] z = x @@ -39,6 +40,7 @@ def reverse_digits(x: num) -> num: def test_more_complex_repeater(): more_complex_repeater = """ +@public def repeat() -> num: out = 0 for i in range(6): @@ -54,6 +56,7 @@ def repeat() -> num: def test_offset_repeater(): offset_repeater = """ +@public def sum() -> num: out = 0 for i in range(80, 121): @@ -69,6 +72,7 @@ def sum() -> num: def test_offset_repeater_2(): offset_repeater_2 = """ +@public def sum(frm: num, to: num) -> num: out = 0 for i in range(frm, frm + 101): diff --git a/tests/parser/features/test_assignment.py b/tests/parser/features/test_assignment.py index f62d1641b0..45188549bf 100644 --- a/tests/parser/features/test_assignment.py +++ b/tests/parser/features/test_assignment.py @@ -5,26 +5,31 @@ def test_augassign_test(): augassign_test = """ +@public def augadd(x: num, y: num) -> num: z = x z += y return z +@public def augmul(x: num, y: num) -> num: z = x z *= y return z +@public def augsub(x: num, y: num) -> num: z = x z -= y return z +@public def augdiv(x: num, y: num) -> num: z = x z /= y return z +@public def augmod(x: num, y: num) -> num: z = x z %= y diff --git a/tests/parser/features/test_clampers.py b/tests/parser/features/test_clampers.py index b467a255ac..ced2e6a98b 100644 --- a/tests/parser/features/test_clampers.py +++ b/tests/parser/features/test_clampers.py @@ -5,6 +5,7 @@ def test_clamper_test_code(): clamper_test_code = """ +@public def foo(s: bytes <= 3) -> bytes <= 3: return s """ diff --git a/tests/parser/features/test_comments.py b/tests/parser/features/test_comments.py index 2b6018b579..045a019f9f 100644 --- a/tests/parser/features/test_comments.py +++ b/tests/parser/features/test_comments.py @@ -5,7 +5,7 @@ def test_comment_test(): comment_test = """ - +@public def foo() -> num: # Returns 3 return 3 diff --git a/tests/parser/features/test_conditionals.py b/tests/parser/features/test_conditionals.py index a8300112fa..82bb8728b1 100644 --- a/tests/parser/features/test_conditionals.py +++ b/tests/parser/features/test_conditionals.py @@ -5,6 +5,7 @@ def test_conditional_return_code(): conditional_return_code = """ +@public def foo(i: bool) -> num: if i: return 5 diff --git a/tests/parser/features/test_constructor.py b/tests/parser/features/test_constructor.py index 63738e45bf..8d05ee79c6 100644 --- a/tests/parser/features/test_constructor.py +++ b/tests/parser/features/test_constructor.py @@ -6,9 +6,12 @@ def test_init_argument_test(): init_argument_test = """ moose: num + +@public def __init__(_moose: num): self.moose = _moose +@public def returnMoose() -> num: return self.moose """ @@ -22,9 +25,11 @@ def test_constructor_advanced_code(): constructor_advanced_code = """ twox: num +@public def __init__(x: num): self.twox = x * 2 +@public def get_twox() -> num: return self.twox """ @@ -36,9 +41,11 @@ def test_constructor_advanced_code2(): constructor_advanced_code2 = """ comb: num +@public def __init__(x: num[2], y: bytes <= 3, z: num): self.comb = x[0] * 1000 + x[1] * 100 + len(y) * 10 + z +@public def get_comb() -> num: return self.comb """ @@ -49,6 +56,7 @@ def get_comb() -> num: def test_large_input_code(): large_input_code = """ +@public def foo(x: num) -> num: return 3 """ @@ -66,9 +74,11 @@ def foo(x: num) -> num: def test_large_input_code_2(): large_input_code_2 = """ +@public def __init__(x: num): y = x +@public def foo() -> num: return 5 """ diff --git a/tests/parser/features/test_external_contract_calls.py b/tests/parser/features/test_external_contract_calls.py index 9cf23aadca..17ba007c7e 100644 --- a/tests/parser/features/test_external_contract_calls.py +++ b/tests/parser/features/test_external_contract_calls.py @@ -5,6 +5,7 @@ def test_external_contract_calls(): contract_1 = """ +@public def foo(arg1: num) -> num: return arg1 """ @@ -15,6 +16,7 @@ def foo(arg1: num) -> num: class Foo(): def foo(arg1: num) -> num: pass +@public def bar(arg1: address, arg2: num) -> num: return Foo(arg1).foo(arg2) """ @@ -28,12 +30,15 @@ def test_complicated_external_contract_calls(): contract_1 = """ lucky: public(num) +@public def __init__(_lucky: num): self.lucky = _lucky +@public def foo() -> num: return self.lucky +@public def array() -> bytes <= 3: return 'dog' """ @@ -46,6 +51,7 @@ class Foo(): def foo() -> num: pass def array() -> bytes <= 3: pass +@public def bar(arg1: address) -> num: return Foo(arg1).foo() """ @@ -57,6 +63,7 @@ def bar(arg1: address) -> num: def test_external_contract_calls_with_bytes(): contract_1 = """ +@public def array() -> bytes <= 3: return 'dog' """ @@ -67,6 +74,7 @@ def array() -> bytes <= 3: class Foo(): def array() -> bytes <= 3: pass +@public def get_array(arg1: address) -> bytes <= 3: return Foo(arg1).array() """ @@ -79,6 +87,7 @@ def test_external_contract_call_state_change(): contract_1 = """ lucky: public(num) +@public def set_lucky(_lucky: num): self.lucky = _lucky """ @@ -90,6 +99,7 @@ def set_lucky(_lucky: num): class Foo(): def set_lucky(_lucky: num): pass +@public def set_lucky(arg1: address, arg2: num): Foo(arg1).set_lucky(arg2) """ @@ -136,6 +146,7 @@ def test_external_contract_can_be_changed_based_on_address(): contract_1 = """ lucky: public(num) +@public def set_lucky(_lucky: num): self.lucky = _lucky """ @@ -146,6 +157,7 @@ def set_lucky(_lucky: num): contract_2 = """ lucky: public(num) +@public def set_lucky(_lucky: num): self.lucky = _lucky """ @@ -157,6 +169,7 @@ def set_lucky(_lucky: num): class Foo(): def set_lucky(_lucky: num): pass +@public def set_lucky(arg1: address, arg2: num): Foo(arg1).set_lucky(arg2) """ @@ -173,6 +186,7 @@ def test_external_contract_calls_with_public_globals(): contract_1 = """ lucky: public(num) +@public def __init__(_lucky: num): self.lucky = _lucky """ @@ -184,6 +198,7 @@ def __init__(_lucky: num): class Foo(): def get_lucky() -> num: pass +@public def bar(arg1: address) -> num: return Foo(arg1).get_lucky() """ @@ -197,6 +212,7 @@ def test_external_contract_calls_with_multiple_contracts(): contract_1 = """ lucky: public(num) +@public def __init__(_lucky: num): self.lucky = _lucky """ @@ -210,6 +226,7 @@ def get_lucky() -> num: pass magic_number: public(num) +@public def __init__(arg1: address): self.magic_number = Foo(arg1).get_lucky() """ @@ -221,6 +238,7 @@ def get_magic_number() -> num: pass best_number: public(num) +@public def __init__(arg1: address): self.best_number = Bar(arg1).get_magic_number() """ @@ -232,6 +250,7 @@ def __init__(arg1: address): def test_invalid_external_contract_call_to_the_same_contract(assert_tx_failed): contract_1 = """ +@public def bar() -> num: return 1 """ @@ -240,12 +259,15 @@ def bar() -> num: class Bar(): def bar() -> num: pass +@public def bar() -> num: return 1 +@public def _stmt(x: address): Bar(x).bar() +@public def _expr(x: address) -> num: return Bar(x).bar() """ @@ -262,6 +284,7 @@ def _expr(x: address) -> num: def test_invalid_nonexistent_contract_call(assert_tx_failed): contract_1 = """ +@public def bar() -> num: return 1 """ @@ -270,6 +293,7 @@ def bar() -> num: class Bar(): def bar() -> num: pass +@public def foo(x: address) -> num: return Bar(x).bar() """ @@ -290,6 +314,7 @@ class Bar(): best_number: public(num) +@public def __init__(): pass """ @@ -299,6 +324,7 @@ def __init__(): def test_invalid_contract_reference_call(assert_tx_failed): contract = """ +@public def bar(arg1: address, arg2: num) -> num: return Foo(arg1).foo(arg2) """ @@ -311,6 +337,7 @@ def test_invalid_contract_reference_return_type(assert_tx_failed): class Foo(): def foo(arg2: num) -> invalid: pass +@public def bar(arg1: address, arg2: num) -> num: return Foo(arg1).foo(arg2) """ @@ -344,7 +371,7 @@ def foo(arg2: num) -> num: pass def test_external_contracts_must_be_declared_first_3(assert_tx_failed): contract = """ - +@public def foo() -> num: return 1 diff --git a/tests/parser/features/test_gas.py b/tests/parser/features/test_gas.py index c79baadc9b..e2ea293ce7 100644 --- a/tests/parser/features/test_gas.py +++ b/tests/parser/features/test_gas.py @@ -8,7 +8,7 @@ def test_gas_call(): gas_call = """ - +@public def foo() -> num: return msg.gas """ @@ -24,7 +24,7 @@ def test_gas_estimate_repr(): code = """ x: num - +@public def __init__(): self.x = 1 """ diff --git a/tests/parser/features/test_internal_call.py b/tests/parser/features/test_internal_call.py index 5c1e676090..caa9e23430 100644 --- a/tests/parser/features/test_internal_call.py +++ b/tests/parser/features/test_internal_call.py @@ -5,9 +5,11 @@ def test_selfcall_code(): selfcall_code = """ +@public def foo() -> num: return 3 +@public def bar() -> num: return self.foo() """ @@ -20,15 +22,19 @@ def bar() -> num: def test_selfcall_code_2(): selfcall_code_2 = """ +@public def double(x: num) -> num: return x * 2 +@public def returnten() -> num: return self.double(5) +@public def _hashy(x: bytes32) -> bytes32: return sha3(x) +@public def return_hash_of_rzpadded_cow() -> bytes32: return self._hashy(0x636f770000000000000000000000000000000000000000000000000000000000) """ @@ -42,15 +48,19 @@ def return_hash_of_rzpadded_cow() -> bytes32: def test_selfcall_code_3(): selfcall_code_3 = """ +@public def _hashy2(x: bytes <= 100) -> bytes32: return sha3(x) +@public def return_hash_of_cow_x_30() -> bytes32: return self._hashy2("cowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcow") +@public def _len(x: bytes <= 100) -> num: return len(x) +@public def returnten() -> num: return self._len("badminton!") """ @@ -64,27 +74,35 @@ def returnten() -> num: def test_selfcall_code_4(): selfcall_code_4 = """ +@public def summy(x: num, y: num) -> num: return x + y +@public def catty(x: bytes <= 5, y: bytes <= 5) -> bytes <= 10: return concat(x, y) +@public def slicey1(x: bytes <= 10, y: num) -> bytes <= 10: return slice(x, start=0, len=y) +@public def slicey2(y: num, x: bytes <= 10) -> bytes <= 10: return slice(x, start=0, len=y) +@public def returnten() -> num: return self.summy(3, 7) +@public def return_mongoose() -> bytes <= 10: return self.catty("mon", "goose") +@public def return_goose() -> bytes <= 10: return self.slicey1("goosedog", 5) +@public def return_goose2() -> bytes <= 10: return self.slicey2(5, "goosedog") """ @@ -102,9 +120,11 @@ def test_selfcall_code_5(): selfcall_code_5 = """ counter: num +@public def increment(): self.counter += 1 +@public def returnten() -> num: for i in range(10): self.increment() @@ -120,15 +140,19 @@ def test_selfcall_code_6(): selfcall_code_6 = """ excls: bytes <= 32 +@public def set_excls(arg: bytes <= 32): self.excls = arg +@public def underscore() -> bytes <= 1: return "_" +@public def hardtest(x: bytes <= 100, y: num, z: num, a: bytes <= 100, b: num, c: num) -> bytes <= 201: return concat(slice(x, start=y, len=z), self.underscore(), slice(a, start=b, len=c)) +@public def return_mongoose_revolution_32_excls() -> bytes <= 201: self.set_excls("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") return self.hardtest("megamongoose123", 4, 8, concat("russian revolution", self.excls), 8, 42) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index c7832178c1..1ee7e8a90a 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -8,6 +8,7 @@ def test_empy_event_logging(): loggy_code = """ MyLog: __log__({}) +@public def foo(): log.MyLog() """ @@ -30,6 +31,7 @@ def test_event_logging_with_topics(): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3)}) +@public def foo(): log.MyLog('bar') """ @@ -52,6 +54,7 @@ def test_event_logging_with_multiple_topics(): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3), arg2: indexed(bytes <= 4), arg3: indexed(address)}) +@public def foo(): log.MyLog('bar', 'home', self) """ @@ -74,10 +77,12 @@ def test_logging_the_same_event_multiple_times_with_topics(): loggy_code = """ MyLog: __log__({arg1: indexed(num), arg2: indexed(address)}) +@public def foo(): log.MyLog(1, self) log.MyLog(1, self) +@public def bar(): log.MyLog(1, self) log.MyLog(1, self) @@ -102,6 +107,7 @@ def test_event_logging_cannot_have_more_than_three_topics(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3), arg2: indexed(bytes <= 4), arg3: indexed(address), arg4: indexed(num)}) +@public def foo(): log.MyLog('bar', 'home', self) """ @@ -113,6 +119,7 @@ def test_event_logging_with_data(): loggy_code = """ MyLog: __log__({arg1: num}) +@public def foo(): log.MyLog(123) """ @@ -135,6 +142,7 @@ def test_event_loggging_with_fixed_array_data(): loggy_code = """ MyLog: __log__({arg1: num[2], arg2: timestamp[3], arg3: num[2][2]}) +@public def foo(): log.MyLog([1,2], [block.timestamp, block.timestamp+1, block.timestamp+2], [[1,2],[1,2]]) log.MyLog([1,2], [block.timestamp, block.timestamp+1, block.timestamp+2], [[1,2],[1,2]]) @@ -159,6 +167,7 @@ def test_logging_with_input_bytes(bytes_helper): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 4), arg2: indexed(bytes <= 29), arg3: bytes<=31}) +@public def foo(arg1: bytes <= 29, arg2: bytes <= 31): log.MyLog('bar', arg1, arg2) """ @@ -180,11 +189,9 @@ def test_event_logging_with_data_with_different_types(): loggy_code = """ MyLog: __log__({arg1: num, arg2: bytes <= 4, arg3: bytes <= 3, arg4: address, arg5: address, arg6: timestamp}) +@public def foo(): log.MyLog(123, 'home', 'bar', 0xc305c901078781C232A2a521C2aF7980f8385ee9, self, block.timestamp) -# MyLog: __log__({arg1: bytes <= 3, arg2: bytes <= 5}) -# def foo(): -# log.MyLog('bar', 'bears') """ c = get_contract_with_gas_estimation(loggy_code) @@ -205,6 +212,7 @@ def test_event_logging_with_topics_and_data(): loggy_code = """ MyLog: __log__({arg1: indexed(num), arg2: bytes <= 3}) +@public def foo(): log.MyLog(1, 'bar') """ @@ -228,6 +236,7 @@ def test_event_logging_with_multiple_logs_topics_and_data(): MyLog: __log__({arg1: indexed(num), arg2: bytes <= 3}) YourLog: __log__({arg1: indexed(address), arg2: bytes <= 5}) +@public def foo(): log.MyLog(1, 'bar') log.YourLog(self, 'house') @@ -256,6 +265,8 @@ def foo(): def test_fails_when_input_is_the_wrong_type(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: indexed(num)}) + +@public def foo_(): log.MyLog('yo') """ @@ -266,6 +277,8 @@ def foo_(): def test_fails_when_topic_is_the_wrong_size(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3)}) + +@public def foo(): log.MyLog('bars') """ @@ -276,6 +289,8 @@ def foo(): def test_fails_when_input_topic_is_the_wrong_size(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3)}) + +@public def foo(arg1: bytes <= 4): log.MyLog(arg1) """ @@ -286,6 +301,8 @@ def foo(arg1: bytes <= 4): def test_fails_when_data_is_the_wrong_size(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: bytes <= 3}) + +@public def foo(): log.MyLog('bars') """ @@ -296,6 +313,8 @@ def foo(): def test_fails_when_input_data_is_the_wrong_size(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: bytes <= 3}) + +@public def foo(arg1: bytes <= 4): log.MyLog(arg1) """ @@ -306,6 +325,8 @@ def foo(arg1: bytes <= 4): def test_fails_when_log_data_is_over_32_bytes(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: bytes <= 100}) + +@public def foo(): pass """ @@ -316,6 +337,7 @@ def foo(): def test_logging_fails_with_over_three_topics(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: indexed(num), arg2: indexed(num), arg3: indexed(num), arg4: indexed(num)}) +@public def __init__(): log.MyLog(1, 2, 3, 4) """ @@ -328,6 +350,7 @@ def test_logging_fails_with_duplicate_log_names(assert_tx_failed): MyLog: __log__({}) MyLog: __log__({}) +@public def foo(): log.MyLog() """ @@ -337,6 +360,8 @@ def foo(): def test_logging_fails_with_when_log_is_undeclared(assert_tx_failed): loggy_code = """ + +@public def foo(): log.MyLog() """ @@ -348,6 +373,7 @@ def test_logging_fails_with_topic_type_mismatch(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: indexed(num)}) +@public def foo(): log.MyLog(self) """ @@ -359,6 +385,7 @@ def test_logging_fails_with_data_type_mismatch(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: bytes <= 3}) +@public def foo(): log.MyLog(self) """ @@ -377,6 +404,8 @@ def test_logging_fails_after_a_global_declaration(assert_tx_failed): def test_logging_fails_after_a_function_declaration(assert_tx_failed): loggy_code = """ + +@public def foo(): pass @@ -390,16 +419,20 @@ def test_loggy_code(): loggy_code = """ s: bytes <= 100 +@public def foo(): raw_log([], "moo") +@public def goo(): raw_log([0x1234567812345678123456781234567812345678123456781234567812345678], "moo2") +@public def hoo(): self.s = "moo3" raw_log([], self.s) +@public def ioo(inp: bytes <= 100): raw_log([], inp) """ diff --git a/tests/parser/features/test_packing.py b/tests/parser/features/test_packing.py index a365a8942b..a73d28309b 100644 --- a/tests/parser/features/test_packing.py +++ b/tests/parser/features/test_packing.py @@ -10,6 +10,7 @@ def test_packing_test(): z: {foo: num[3], bar: {a: num, b: num}[2]} a: num +@public def foo() -> num: self.x = 1 self.y[0] = 2 @@ -24,6 +25,7 @@ def foo() -> num: return self.x + self.y[0] + self.y[4] + self.z.foo[0] + self.z.foo[2] + \ self.z.bar[0].a + self.z.bar[0].b + self.z.bar[1].a + self.z.bar[1].b + self.a +@public def fop() -> num: _x: num _y: num[5] diff --git a/tests/parser/functions/test_bitwise.py b/tests/parser/functions/test_bitwise.py index 492e21d03c..9ab62c0a04 100644 --- a/tests/parser/functions/test_bitwise.py +++ b/tests/parser/functions/test_bitwise.py @@ -5,18 +5,23 @@ def test_test_bitwise(): test_bitwise = """ +@public def _bitwise_and(x: num256, y: num256) -> num256: return bitwise_and(x, y) +@public def _bitwise_or(x: num256, y: num256) -> num256: return bitwise_or(x, y) +@public def _bitwise_xor(x: num256, y: num256) -> num256: return bitwise_xor(x, y) +@public def _bitwise_not(x: num256) -> num256: return bitwise_not(x) +@public def _shift(x: num256, y: num) -> num256: return shift(x, y) """ diff --git a/tests/parser/functions/test_block_number.py b/tests/parser/functions/test_block_number.py index daae43ebbc..c5b65114c3 100644 --- a/tests/parser/functions/test_block_number.py +++ b/tests/parser/functions/test_block_number.py @@ -5,6 +5,7 @@ def test_block_number(): block_number_code = """ +@public def block_number() -> num: return block.number """ diff --git a/tests/parser/functions/test_concat.py b/tests/parser/functions/test_concat.py index f8d2514107..5d760a72b8 100644 --- a/tests/parser/functions/test_concat.py +++ b/tests/parser/functions/test_concat.py @@ -5,9 +5,11 @@ def test_concat(): test_concat = """ +@public def foo2(input1: bytes <= 50, input2: bytes <= 50) -> bytes <= 1000: return concat(input1, input2) +@public def foo3(input1: bytes <= 50, input2: bytes <= 50, input3: bytes <= 50) -> bytes <= 1000: return concat(input1, input2, input3) """ @@ -26,6 +28,7 @@ def foo3(input1: bytes <= 50, input2: bytes <= 50, input3: bytes <= 50) -> bytes def test_concat2(): test_concat2 = """ +@public def foo(inp: bytes <= 50) -> bytes <= 1000: x = inp return concat(x, inp, x, inp, x, inp, x, inp, x, inp) @@ -40,6 +43,7 @@ def test_crazy_concat_code(): crazy_concat_code = """ y: bytes <= 10 +@public def krazykonkat(z: bytes <= 10) -> bytes <= 25: x = "cow" self.y = "horse" @@ -55,9 +59,11 @@ def krazykonkat(z: bytes <= 10) -> bytes <= 25: def test_concat_bytes32(): test_concat_bytes32 = """ +@public def sandwich(inp: bytes <= 100, inp2: bytes32) -> bytes <= 164: return concat(inp2, inp, inp2) +@public def fivetimes(inp: bytes32) -> bytes <= 160: return concat(inp, inp, inp, inp, inp) """ @@ -77,14 +83,17 @@ def test_konkat_code(): konkat_code = """ ecks: bytes32 +@public def foo(x: bytes32, y: bytes32) -> bytes <= 64: selfecks = x return concat(selfecks, y) +@public def goo(x: bytes32, y: bytes32) -> bytes <= 64: self.ecks = x return concat(self.ecks, y) +@public def hoo(x: bytes32, y: bytes32) -> bytes <= 64: return concat(x, y) """ diff --git a/tests/parser/functions/test_ec.py b/tests/parser/functions/test_ec.py index 637a5bf6e4..d2b02b8fb9 100644 --- a/tests/parser/functions/test_ec.py +++ b/tests/parser/functions/test_ec.py @@ -9,14 +9,17 @@ def test_ecadd(): x3: num256[2] y3: num256[2] +@public def _ecadd(x: num256[2], y: num256[2]) -> num256[2]: return ecadd(x, y) +@public def _ecadd2(x: num256[2], y: num256[2]) -> num256[2]: x2 = x y2 = [y[0], y[1]] return ecadd(x2, y2) +@public def _ecadd3(x: num256[2], y: num256[2]) -> num256[2]: self.x3 = x self.y3 = [y[0], y[1]] @@ -35,14 +38,17 @@ def test_ecmul(): x3: num256[2] y3: num256 +@public def _ecmul(x: num256[2], y: num256) -> num256[2]: return ecmul(x, y) +@public def _ecmul2(x: num256[2], y: num256) -> num256[2]: x2 = x y2 = y return ecmul(x2, y2) +@public def _ecmul3(x: num256[2], y: num256) -> num256[2]: self.x3 = x self.y3 = y diff --git a/tests/parser/functions/test_ecrecover.py b/tests/parser/functions/test_ecrecover.py index 14bd17c24d..9aa177c944 100644 --- a/tests/parser/functions/test_ecrecover.py +++ b/tests/parser/functions/test_ecrecover.py @@ -5,9 +5,11 @@ def test_ecrecover_test(): ecrecover_test = """ +@public def test_ecrecover(h: bytes32, v:num256, r:num256, s:num256) -> address: return ecrecover(h, v, r, s) +@public def test_ecrecover2() -> address: return ecrecover(0x3535353535353535353535353535353535353535353535353535353535353535, as_num256(28), diff --git a/tests/parser/functions/test_extract32.py b/tests/parser/functions/test_extract32.py index b2bc2b757a..f7cf60ad34 100644 --- a/tests/parser/functions/test_extract32.py +++ b/tests/parser/functions/test_extract32.py @@ -6,13 +6,16 @@ def test_extract32_code(): extract32_code = """ y: bytes <= 100 +@public def extrakt32(inp: bytes <= 100, index: num) -> bytes32: return extract32(inp, index) +@public def extrakt32_mem(inp: bytes <= 100, index: num) -> bytes32: x = inp return extract32(x, index) +@public def extrakt32_storage(index: num, inp: bytes <= 100) -> bytes32: self.y = inp return extract32(self.y, index) @@ -55,18 +58,23 @@ def extrakt32_storage(index: num, inp: bytes <= 100) -> bytes32: def test_extract32_code(): extract32_code = """ +@public def foo(inp: bytes <= 32) -> num: return extract32(inp, 0, type=num128) +@public def bar(inp: bytes <= 32) -> num256: return extract32(inp, 0, type=num256) +@public def baz(inp: bytes <= 32) -> bytes32: return extract32(inp, 0, type=bytes32) +@public def fop(inp: bytes <= 32) -> bytes32: return extract32(inp, 0) +@public def foq(inp: bytes <= 32) -> address: return extract32(inp, 0, type=address) """ diff --git a/tests/parser/functions/test_is_contract.py b/tests/parser/functions/test_is_contract.py index c7e6b9ab14..961fec3fe1 100644 --- a/tests/parser/functions/test_is_contract.py +++ b/tests/parser/functions/test_is_contract.py @@ -5,12 +5,14 @@ def test_is_contract(): contract_1 = """ +@public def foo(arg1: address) -> bool: result = arg1.is_contract return result """ contract_2 = """ +@public def foo(arg1: address) -> bool: return arg1.is_contract """ diff --git a/tests/parser/functions/test_length.py b/tests/parser/functions/test_length.py index 781dc382b1..be71c5f1fb 100644 --- a/tests/parser/functions/test_length.py +++ b/tests/parser/functions/test_length.py @@ -6,6 +6,8 @@ def test_test_length(): test_length = """ y: bytes <= 10 + +@public def foo(inp: bytes <= 10) -> num: x = slice(inp, start=1, len=5) self.y = slice(inp, start=2, len=4) diff --git a/tests/parser/functions/test_method_id.py b/tests/parser/functions/test_method_id.py index 19a97e6a6c..fe0228a9e2 100644 --- a/tests/parser/functions/test_method_id.py +++ b/tests/parser/functions/test_method_id.py @@ -5,9 +5,11 @@ def test_method_id_test(): method_id_test = """ +@public def double(x: num) -> num: return x * 2 +@public def returnten() -> num: ans = raw_call(self, concat(method_id("double(int128)"), as_bytes32(5)), gas=50000, outsize=32) return as_num128(extract32(ans, 0)) diff --git a/tests/parser/functions/test_minmax.py b/tests/parser/functions/test_minmax.py index 0afd28316f..cbe94e3ac4 100644 --- a/tests/parser/functions/test_minmax.py +++ b/tests/parser/functions/test_minmax.py @@ -5,9 +5,11 @@ def test_minmax(): minmax_test = """ +@public def foo() -> decimal: return min(3, 5) + max(10, 20) + min(200.1, 400) + max(3000, 8000.02) + min(50000.003, 70000.004) +@public def goo() -> num256: return num256_add(min(as_num256(3), as_num256(5)), max(as_num256(40), as_num256(80))) """ diff --git a/tests/parser/functions/test_only_init_abi.py b/tests/parser/functions/test_only_init_abi.py index 41f81ef48d..e554b8d2ef 100644 --- a/tests/parser/functions/test_only_init_abi.py +++ b/tests/parser/functions/test_only_init_abi.py @@ -5,12 +5,14 @@ def test_only_init_function(): code = """ x: num +@public def __init__(): self.x = 1 """ code_init_empty = """ x: num +@public def __init__(): pass """ diff --git a/tests/parser/functions/test_raw_call.py b/tests/parser/functions/test_raw_call.py index d350d06a47..350e2f6e82 100644 --- a/tests/parser/functions/test_raw_call.py +++ b/tests/parser/functions/test_raw_call.py @@ -5,12 +5,15 @@ def test_caller_code(): caller_code = """ +@public def foo() -> bytes <= 7: return raw_call(0x0000000000000000000000000000000000000004, "moose", gas=50000, outsize=5) +@public def bar() -> bytes <= 7: return raw_call(0x0000000000000000000000000000000000000004, "moose", gas=50000, outsize=3) +@public def baz() -> bytes <= 7: return raw_call(0x0000000000000000000000000000000000000004, "moose", gas=50000, outsize=7) """ @@ -26,6 +29,7 @@ def baz() -> bytes <= 7: def test_multiple_levels(): inner_code = """ +@public def returnten() -> num: return 10 """ @@ -33,11 +37,13 @@ def returnten() -> num: c = get_contract_with_gas_estimation(inner_code) outer_code = """ +@public def create_and_call_returnten(inp: address) -> num: x = create_with_code_of(inp) o = extract32(raw_call(x, "\xd0\x1f\xb1\xb8", outsize=32, gas=50000), 0, type=num128) return o +@public def create_and_return_forwarder(inp: address) -> address: return create_with_code_of(inp) """ @@ -56,6 +62,7 @@ def create_and_return_forwarder(inp: address) -> address: def test_multiple_levels2(): inner_code = """ +@public def returnten() -> num: assert False return 10 @@ -64,11 +71,13 @@ def returnten() -> num: c = get_contract_with_gas_estimation(inner_code) outer_code = """ +@public def create_and_call_returnten(inp: address) -> num: x = create_with_code_of(inp) o = extract32(raw_call(x, "\xd0\x1f\xb1\xb8", outsize=32, gas=50000), 0, type=num128) return o +@public def create_and_return_forwarder(inp: address) -> address: return create_with_code_of(inp) """ diff --git a/tests/parser/functions/test_return_tuple.py b/tests/parser/functions/test_return_tuple.py index 729d6e490c..9b87c2061a 100644 --- a/tests/parser/functions/test_return_tuple.py +++ b/tests/parser/functions/test_return_tuple.py @@ -13,33 +13,41 @@ def test_return_type(): c: num } +@public def __init__(): self.chunk.a = "hello" self.chunk.b = "world" self.chunk.c = 5678 +@public def out() -> (num, address): return 3333, 0x0000000000000000000000000000000000000001 +@public def out_literals() -> (num, address, bytes <= 4): return 1, 0x0000000000000000000000000000000000000000, "random" +@public def out_bytes_first() -> (bytes <= 4, num): return "test", 1234 +@public def out_bytes_a(x: num, y: bytes <= 4) -> (num, bytes <= 4): return x, y +@public def out_bytes_b(x: num, y: bytes <= 4) -> (bytes <= 4, num, bytes <= 4): return y, x, y +@public def four() -> (num, bytes <= 8, bytes <= 8, num): return 1234, "bytes", "test", 4321 - +@public def out_chunk() -> (bytes <= 8, num, bytes <= 8): return self.chunk.a, self.chunk.c, self.chunk.b +@public def out_very_long_bytes() -> (num, bytes <= 1024, num, address): return 5555, "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest", 6666, 0x0000000000000000000000000000000000001234 # noqa """ @@ -58,6 +66,7 @@ def out_very_long_bytes() -> (num, bytes <= 1024, num, address): def test_return_type_signatures(): code = """ +@public def out_literals() -> (num, address, bytes <= 4): return 1, 0x0000000000000000000000000000000000000000, "random" """ diff --git a/tests/parser/functions/test_rlp_list.py b/tests/parser/functions/test_rlp_list.py index facde94970..8d2b283568 100644 --- a/tests/parser/functions/test_rlp_list.py +++ b/tests/parser/functions/test_rlp_list.py @@ -7,49 +7,61 @@ def test_rlp_decoder_code(assert_tx_failed): rlp_decoder_code = """ u: bytes <= 100 +@public def foo() -> address: x = RLPList('\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', [address, bytes32]) return x[0] +@public def fop() -> bytes32: x = RLPList('\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', [address, bytes32]) return x[1] +@public def foq() -> bytes <= 100: x = RLPList('\xc5\x83cow\x03', [bytes, num]) return x[0] +@public def fos() -> num: x = RLPList('\xc5\x83cow\x03', [bytes, num]) return x[1] +@public def fot() -> num256: x = RLPList('\xc5\x83cow\x03', [bytes, num256]) return x[1] +@public def qoo(inp: bytes <= 100) -> address: x = RLPList(inp, [address, bytes32]) return x[0] +@public def qos(inp: bytes <= 100) -> num: x = RLPList(inp, [num, num]) return x[0] + x[1] +@public def qot(inp: bytes <= 100): x = RLPList(inp, [num, num]) +@public def qov(inp: bytes <= 100): x = RLPList(inp, [num256, num256]) +@public def roo(inp: bytes <= 100) -> address: self.u = inp x = RLPList(self.u, [address, bytes32]) return x[0] +@public def too(inp: bytes <= 100) -> bool: x = RLPList(inp, [bool]) return x[0] +@public def voo(inp: bytes <= 1024) -> num: x = RLPList(inp, [num, num, bytes32, num, bytes32, bytes]) return x[1] diff --git a/tests/parser/functions/test_send.py b/tests/parser/functions/test_send.py index 5df063f7ba..e39dd5a700 100644 --- a/tests/parser/functions/test_send.py +++ b/tests/parser/functions/test_send.py @@ -5,10 +5,11 @@ def test_send(assert_tx_failed): send_test = """ - +@public def foo(): send(msg.sender, self.balance+1) +@public def fop(): send(msg.sender, 10) """ diff --git a/tests/parser/functions/test_sha3.py b/tests/parser/functions/test_sha3.py index d461e61ec1..249e83b371 100644 --- a/tests/parser/functions/test_sha3.py +++ b/tests/parser/functions/test_sha3.py @@ -5,9 +5,11 @@ def test_hash_code(): hash_code = """ +@public def foo(inp: bytes <= 100) -> bytes32: return sha3(inp) +@public def bar() -> bytes32: return sha3("inp") """ @@ -21,6 +23,7 @@ def bar() -> bytes32: def test_hash_code2(): hash_code2 = """ +@public def foo(inp: bytes <= 100) -> bool: return sha3(inp) == sha3("badminton") """ @@ -32,16 +35,21 @@ def foo(inp: bytes <= 100) -> bool: def test_hash_code3(): hash_code3 = """ test: bytes <= 100 + +@public def set_test(inp: bytes <= 100): self.test = inp +@public def tryy(inp: bytes <= 100) -> bool: return sha3(inp) == sha3(self.test) +@public def trymem(inp: bytes <= 100) -> bool: x = self.test return sha3(inp) == sha3(x) +@public def try32(inp: bytes32) -> bool: return sha3(inp) == sha3(self.test) """ diff --git a/tests/parser/functions/test_slice.py b/tests/parser/functions/test_slice.py index c348589ec9..29324c632f 100644 --- a/tests/parser/functions/test_slice.py +++ b/tests/parser/functions/test_slice.py @@ -5,12 +5,15 @@ def test_test_slice(): test_slice = """ + +@public def foo(inp1: bytes <= 10) -> bytes <= 3: x = 5 s = slice(inp1, start=3, len=3) y = 7 return s +@public def bar(inp1: bytes <= 10) -> num: x = 5 s = slice(inp1, start=3, len=3) @@ -29,6 +32,7 @@ def bar(inp1: bytes <= 10) -> num: def test_test_slice2(): test_slice2 = """ +@public def slice_tower_test(inp1: bytes <= 50) -> bytes <= 50: inp = inp1 for i in range(1, 11): @@ -48,12 +52,14 @@ def test_test_slice3(): x: num s: bytes <= 50 y: num +@public def foo(inp1: bytes <= 50) -> bytes <= 50: self.x = 5 self.s = slice(inp1, start=3, len=3) self.y = 7 return self.s +@public def bar(inp1: bytes <= 50) -> num: self.x = 5 self.s = slice(inp1, start=3, len=3) @@ -72,6 +78,7 @@ def bar(inp1: bytes <= 50) -> num: def test_test_slice4(): test_slice4 = """ +@public def foo(inp: bytes <= 10, start: num, len: num) -> bytes <= 10: return slice(inp, start=start, len=len) """ diff --git a/tests/parser/globals/test_getters.py b/tests/parser/globals/test_getters.py index 9577fb0332..f5114b1f84 100644 --- a/tests/parser/globals/test_getters.py +++ b/tests/parser/globals/test_getters.py @@ -7,9 +7,11 @@ def test_state_accessor(): state_accessor = """ y: num[num] +@public def oo(): self.y[3] = 5 +@public def foo() -> num: return self.y[3] @@ -36,6 +38,7 @@ def test_getter_code(): g: wei_value }[num]) +@public def __init__(): self.x = as_wei_value(7, wei) self.y[1] = 9 diff --git a/tests/parser/globals/test_globals.py b/tests/parser/globals/test_globals.py index b6a349153c..7608990b12 100644 --- a/tests/parser/globals/test_globals.py +++ b/tests/parser/globals/test_globals.py @@ -5,10 +5,13 @@ def test_permanent_variables_test(): permanent_variables_test = """ var: {a: num, b: num} + +@public def __init__(a: num, b: num): self.var.a = a self.var.b = b +@public def returnMoose() -> num: return self.var.a * 10 + self.var.b """ diff --git a/tests/parser/globals/test_setters.py b/tests/parser/globals/test_setters.py index de3732be90..0e49e0acc3 100644 --- a/tests/parser/globals/test_setters.py +++ b/tests/parser/globals/test_setters.py @@ -7,21 +7,25 @@ def test_multi_setter_test(): multi_setter_test = """ foo: num[3] bar: num[3][3] +@public def foo() -> num: self.foo = [1, 2, 3] return(self.foo[0] + self.foo[1] * 10 + self.foo[2] * 100) +@public def fop() -> num: self.bar[0] = [1, 2, 3] self.bar[1] = [4, 5, 6] return self.bar[0][0] + self.bar[0][1] * 10 + self.bar[0][2] * 100 + \ self.bar[1][0] * 1000 + self.bar[1][1] * 10000 + self.bar[1][2] * 100000 +@public def goo() -> num: goo: num[3] goo = [1, 2, 3] return(goo[0] + goo[1] * 10 + goo[2] * 100) +@public def gop() -> num: # Following a standard naming scheme; nothing to do with the US republican party gar: num[3][3] gar[0] = [1, 2, 3] @@ -29,21 +33,25 @@ def gop() -> num: # Following a standard naming scheme; nothing to do with the U return gar[0][0] + gar[0][1] * 10 + gar[0][2] * 100 + \ gar[1][0] * 1000 + gar[1][1] * 10000 + gar[1][2] * 100000 +@public def hoo() -> num: self.foo = None return(self.foo[0] + self.foo[1] * 10 + self.foo[2] * 100) +@public def hop() -> num: self.bar[1] = None return self.bar[0][0] + self.bar[0][1] * 10 + self.bar[0][2] * 100 + \ self.bar[1][0] * 1000 + self.bar[1][1] * 10000 + self.bar[1][2] * 100000 +@public def joo() -> num: goo: num[3] goo = [1, 2, 3] goo = None return(goo[0] + goo[1] * 10 + goo[2] * 100) +@public def jop() -> num: gar: num[3][3] gar[0] = [1, 2, 3] @@ -71,6 +79,7 @@ def test_multi_setter_struct_test(): foo: {foo: num, bar: num}[3] z: {foo: num[3], bar: {a: num, b: num}[2]}[2] +@public def foo() -> num: self.foo[0] = {foo: 1, bar: 2} self.foo[1] = {foo: 3, bar: 4} @@ -78,6 +87,7 @@ def foo() -> num: return self.foo[0].foo + self.foo[0].bar * 10 + self.foo[1].foo * 100 + \ self.foo[1].bar * 1000 + self.foo[2].foo * 10000 + self.foo[2].bar * 100000 +@public def fop() -> num: self.z = [{foo: [1, 2, 3], bar: [{a: 4, b: 5}, {a: 2, b: 3}]}, {foo: [6, 7, 8], bar: [{a: 9, b: 1}, {a: 7, b: 8}]}] @@ -87,6 +97,7 @@ def fop() -> num: self.z[1].bar[0].a * 10000000000 + self.z[1].bar[0].b * 100000000000 + \ self.z[1].bar[1].a * 1000000000000 + self.z[1].bar[1].b * 10000000000000 +@public def goo() -> num: goo: {foo: num, bar: num}[3] goo[0] = {foo: 1, bar: 2} @@ -95,6 +106,7 @@ def goo() -> num: return goo[0].foo + goo[0].bar * 10 + goo[1].foo * 100 + \ goo[1].bar * 1000 + goo[2].foo * 10000 + goo[2].bar * 100000 +@public def gop() -> num: zed = [{foo: [1, 2, 3], bar: [{a: 4, b: 5}, {a: 2, b: 3}]}, {foo: [6, 7, 8], bar: [{a: 9, b: 1}, {a: 7, b: 8}]}] @@ -120,11 +132,13 @@ def test_type_converter_setter_test(): non: {a: {c: decimal}[3], b:num} pap: decimal[2][2] +@public def foo() -> num: self.mom = {a: [{c: 1}, {c: 2}, {c: 3}], b: 4} self.non = self.mom return floor(self.non.a[0].c + self.non.a[1].c * 10 + self.non.a[2].c * 100 + self.non.b * 1000) +@public def goo() -> num: self.pap = [[1, 2], [3, 4.0]] return floor(self.pap[0][0] + self.pap[0][1] * 10 + self.pap[1][0] * 100 + self.pap[1][1] * 1000) @@ -140,6 +154,8 @@ def test_composite_setter_test(): composite_setter_test = """ mom: {a: {c: num}[3], b:num} qoq: {c: num} + +@public def foo() -> num: self.mom = {a: [{c: 1}, {c: 2}, {c: 3}], b: 4} non = {c: 5} @@ -148,6 +164,7 @@ def foo() -> num: self.mom.a[2] = non return self.mom.a[0].c + self.mom.a[1].c * 10 + self.mom.a[2].c * 100 + self.mom.b * 1000 +@public def fop() -> num: popp = {a: [{c: 1}, {c: 2}, {c: 3}], b: 4} self.qoq = {c: 5} @@ -156,6 +173,7 @@ def fop() -> num: popp.a[2] = self.qoq return popp.a[0].c + popp.a[1].c * 10 + popp.a[2].c * 100 + popp.b * 1000 +@public def foq() -> num: popp = {a: [{c: 1}, {c: 2}, {c: 3}], b: 4} popp.a[0] = None diff --git a/tests/parser/integration/test_basics.py b/tests/parser/integration/test_basics.py index 1ebf8cdb6f..47b8c70e07 100644 --- a/tests/parser/integration/test_basics.py +++ b/tests/parser/integration/test_basics.py @@ -5,6 +5,7 @@ def test_null_code(): null_code = """ +@public def foo(): pass """ @@ -15,7 +16,7 @@ def foo(): def test_basic_code(): basic_code = """ - +@public def foo(x: num) -> num: return x * 2 @@ -27,15 +28,19 @@ def foo(x: num) -> num: def test_selfcall_code_3(): selfcall_code_3 = """ +@public def _hashy2(x: bytes <= 100) -> bytes32: return sha3(x) +@public def return_hash_of_cow_x_30() -> bytes32: return self._hashy2("cowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcowcow") +@public def _len(x: bytes <= 100) -> num: return len(x) +@public def returnten() -> num: return self._len("badminton!") """ diff --git a/tests/parser/integration/test_crowdfund.py b/tests/parser/integration/test_crowdfund.py index 5e05270ce3..dd57ad4963 100644 --- a/tests/parser/integration/test_crowdfund.py +++ b/tests/parser/integration/test_crowdfund.py @@ -14,12 +14,14 @@ def test_crowdfund(): refundIndex: num timelimit: timedelta +@public def __init__(_beneficiary: address, _goal: wei_value, _timelimit: timedelta): self.beneficiary = _beneficiary self.deadline = block.timestamp + _timelimit self.timelimit = _timelimit self.goal = _goal +@public @payable def participate(): assert block.timestamp < self.deadline @@ -28,30 +30,37 @@ def participate(): self.funders[nfi].value = msg.value self.nextFunderIndex = nfi + 1 +@public @constant def expired() -> bool: return block.timestamp >= self.deadline +@public @constant def timestamp() -> timestamp: return block.timestamp +@public @constant def deadline() -> timestamp: return self.deadline +@public @constant def timelimit() -> timedelta: return self.timelimit +@public @constant def reached() -> bool: return self.balance >= self.goal +@public def finalize(): assert block.timestamp >= self.deadline and self.balance >= self.goal selfdestruct(self.beneficiary) +@public def refund(): ind = self.refundIndex for i in range(ind, ind + 30): @@ -108,12 +117,14 @@ def test_crowdfund2(): refundIndex: num timelimit: timedelta +@public def __init__(_beneficiary: address, _goal: wei_value, _timelimit: timedelta): self.beneficiary = _beneficiary self.deadline = block.timestamp + _timelimit self.timelimit = _timelimit self.goal = _goal +@public @payable def participate(): assert block.timestamp < self.deadline @@ -121,30 +132,37 @@ def participate(): self.funders[nfi] = {sender: msg.sender, value: msg.value} self.nextFunderIndex = nfi + 1 +@public @constant def expired() -> bool: return block.timestamp >= self.deadline +@public @constant def timestamp() -> timestamp: return block.timestamp +@public @constant def deadline() -> timestamp: return self.deadline +@public @constant def timelimit() -> timedelta: return self.timelimit +@public @constant def reached() -> bool: return self.balance >= self.goal +@public def finalize(): assert block.timestamp >= self.deadline and self.balance >= self.goal selfdestruct(self.beneficiary) +@public def refund(): ind = self.refundIndex for i in range(ind, ind + 30): diff --git a/tests/parser/integration/test_escrow.py b/tests/parser/integration/test_escrow.py index 86be4cc00c..77a5d0b4c0 100644 --- a/tests/parser/integration/test_escrow.py +++ b/tests/parser/integration/test_escrow.py @@ -9,16 +9,19 @@ def test_arbitration_code(): seller: address arbitrator: address +@public def setup(_seller: address, _arbitrator: address): if not self.buyer: self.buyer = msg.sender self.seller = _seller self.arbitrator = _arbitrator +@public def finalize(): assert msg.sender == self.buyer or msg.sender == self.arbitrator send(self.seller, self.balance) +@public def refund(): assert msg.sender == self.seller or msg.sender == self.arbitrator send(self.buyer, self.balance) @@ -44,6 +47,7 @@ def test_arbitration_code_with_init(): seller: address arbitrator: address +@public @payable def __init__(_seller: address, _arbitrator: address): if not self.buyer: @@ -51,10 +55,12 @@ def __init__(_seller: address, _arbitrator: address): self.seller = _seller self.arbitrator = _arbitrator +@public def finalize(): assert msg.sender == self.buyer or msg.sender == self.arbitrator send(self.seller, self.balance) +@public def refund(): assert msg.sender == self.seller or msg.sender == self.arbitrator send(self.buyer, self.balance) diff --git a/tests/parser/syntax/test_as_num256.py b/tests/parser/syntax/test_as_num256.py index 06c2861424..1215cd7497 100644 --- a/tests/parser/syntax/test_as_num256.py +++ b/tests/parser/syntax/test_as_num256.py @@ -7,10 +7,12 @@ fail_list = [ """ +@public def convert2(inp: num256) -> address: return as_bytes32(inp) """, """ +@public def modtest(x: num256, y: num) -> num256: return x % y """ @@ -26,14 +28,17 @@ def test_as_wei_fail(bad_code): valid_list = [ """ +@public def convert1(inp: bytes32) -> num256: return as_num256(inp) """, """ +@public def convert1(inp: bytes32) -> num256: return as_num256(inp) """, """ +@public def convert2(inp: num256) -> bytes32: return as_bytes32(inp) """ diff --git a/tests/parser/syntax/test_as_wei.py b/tests/parser/syntax/test_as_wei.py index 1acaf33f26..e3d90064cd 100644 --- a/tests/parser/syntax/test_as_wei.py +++ b/tests/parser/syntax/test_as_wei.py @@ -7,10 +7,12 @@ fail_list = [ """ +@public def foo(): x = as_wei_value(5, 'szabo') """, """ +@public def foo() -> num(wei): x = 45 return x.balance @@ -27,19 +29,23 @@ def test_as_wei_fail(bad_code): valid_list = [ """ +@public def foo(): x = as_wei_value(5, finney) + as_wei_value(2, babbage) + as_wei_value(8, shannon) """, """ +@public def foo(): z = 2 + 3 x = as_wei_value(2 + 3, finney) """, """ +@public def foo(): x = as_wei_value(5.182, ada) """, """ +@public def foo() -> num(wei): x = 0x1234567890123456789012345678901234567890 return x.balance diff --git a/tests/parser/syntax/test_block.py b/tests/parser/syntax/test_block.py index 711e57f2d2..14f78b3f9d 100644 --- a/tests/parser/syntax/test_block.py +++ b/tests/parser/syntax/test_block.py @@ -7,64 +7,77 @@ fail_list = [ """ +@public def foo() -> num: x = create_with_code_of(0x1234567890123456789012345678901234567890, value=block.timestamp) return 5 """, """ +@public def foo() -> num[2]: return [3,block.timestamp] """, """ +@public def foo() -> timedelta[2]: return [block.timestamp - block.timestamp, block.timestamp] """, """ +@public def foo() -> num(wei / sec): x = as_wei_value(5, finney) y = block.timestamp + 50 return x / y """, """ +@public def foo(): x = slice("cow", start=0, len=block.timestamp) """, """ +@public def foo(): x = 7 y = min(x, block.timestamp) """, """ +@public def foo(): y = min(block.timestamp + 30 - block.timestamp, block.timestamp) """, """ a: num[timestamp] +@public def add_record(): self.a[block.timestamp] = block.timestamp + 20 """, """ a: timestamp[num] +@public def add_record(): self.a[block.timestamp] = block.timestamp + 20 """, """ +@public def add_record(): a = {x: block.timestamp} b = {y: 5} a.x = b.y """, """ +@public def foo(inp: bytes <= 10) -> bytes <= 3: return slice(inp, start=block.timestamp, len=3) """, """ +@public def foo() -> address: return as_unitless_number(block.coinbase) """, (""" +@public def foo() -> num: return block.fail """, Exception) @@ -86,33 +99,41 @@ def test_block_fail(bad_code): """ a: timestamp[timestamp] + +@public def add_record(): self.a[block.timestamp] = block.timestamp + 20 """, """ +@public def foo() -> num(wei / sec): x = as_wei_value(5, finney) y = block.timestamp + 50 - block.timestamp return x / y """, """ +@public def foo() -> timestamp[2]: return [block.timestamp + 86400, block.timestamp] """, """ +@public def foo(): y = min(block.timestamp + 30, block.timestamp + 50) """, """ +@public def foo() -> num: return as_unitless_number(block.timestamp) """, """ +@public def add_record(): a = {x: block.timestamp} a.x = 5 """, """ +@public def foo(): x = block.difficulty + 185 if tx.origin == self: diff --git a/tests/parser/syntax/test_bool.py b/tests/parser/syntax/test_bool.py index c806097cf5..827adb8f69 100644 --- a/tests/parser/syntax/test_bool.py +++ b/tests/parser/syntax/test_bool.py @@ -7,24 +7,29 @@ fail_list = [ """ +@public def foo(): x = true x = 5 """, (""" +@public def foo(): True = 3 """, SyntaxError), """ +@public def foo(): x = True x = 129 """, """ +@public def foo() -> bool: return (1 == 2) <= (1 == 1) """, """ +@public def foo() -> bool: return (1 == 2) or 3 """ @@ -44,41 +49,50 @@ def test_bool_fail(bad_code): valid_list = [ """ +@public def foo(): x = true z = x and false """, """ +@public def foo(): x = true z = x and False """, """ +@public def foo(): x = True x = False """, """ +@public def foo() -> bool: return 1 == 1 """, """ +@public def foo() -> bool: return 1 != 1 """, """ +@public def foo() -> bool: return 1 > 1 """, """ +@public def foo() -> bool: return 1. >= 1 """, """ +@public def foo() -> bool: return 1 < 1 """, """ +@public def foo() -> bool: return 1 <= 1. """ diff --git a/tests/parser/syntax/test_byte_string.py b/tests/parser/syntax/test_byte_string.py index 7321d03100..10c3d8df80 100644 --- a/tests/parser/syntax/test_byte_string.py +++ b/tests/parser/syntax/test_byte_string.py @@ -7,10 +7,12 @@ valid_list = [ """ +@public def foo() -> bytes <= 10: return "badminton" """, """ +@public def foo(): x = "¡très bien!" """ diff --git a/tests/parser/syntax/test_bytes.py b/tests/parser/syntax/test_bytes.py index 86933fb4a7..330276de93 100644 --- a/tests/parser/syntax/test_bytes.py +++ b/tests/parser/syntax/test_bytes.py @@ -7,47 +7,56 @@ fail_list = [ """ +@public def baa(): x: bytes <= 50 y: bytes <= 50 z = x + y """, """ +@public def baa(): x: bytes <= 50 y: num y = x """, """ +@public def baa(): x: bytes <= 50 y: num x = y """, """ +@public def baa(): x: bytes <= 50 y: bytes <= 60 x = y """, """ +@public def foo(x: bytes <= 100) -> bytes <= 75: return x """, """ +@public def foo(x: bytes <= 100) -> num: return x """, """ +@public def foo(x: num) -> bytes <= 75: return x """, """ +@public def foo() -> bytes <= 10: x = '0x1234567890123456789012345678901234567890' x = 0x1234567890123456789012345678901234567890 """, """ +@public def foo() -> bytes <= 10: return "badmintonzz" """ @@ -63,18 +72,22 @@ def test_bytes_fail(bad_code): valid_list = [ """ +@public def foo(x: bytes <= 100) -> bytes <= 100: return x """, """ +@public def foo(x: bytes <= 100) -> bytes <= 150: return x """, """ +@public def convert2(inp: num256) -> bytes32: return as_bytes32(inp) """, """ +@public def baa(): x: bytes <= 50 """ diff --git a/tests/parser/syntax/test_code_size.py b/tests/parser/syntax/test_code_size.py index 76e2876d40..8bb1cab5f5 100644 --- a/tests/parser/syntax/test_code_size.py +++ b/tests/parser/syntax/test_code_size.py @@ -7,11 +7,13 @@ fail_list = [ """ +@public def foo() -> num: x = 45 return x.codesize """, """ +@public def foo() -> num(wei): x = 0x1234567890123456789012345678901234567890 return x.codesize @@ -28,6 +30,7 @@ def test_block_fail(bad_code): valid_list = [ """ +@public def foo() -> num: x = 0x1234567890123456789012345678901234567890 return x.codesize diff --git a/tests/parser/syntax/test_concat.py b/tests/parser/syntax/test_concat.py index 63a1029153..467d98a074 100644 --- a/tests/parser/syntax/test_concat.py +++ b/tests/parser/syntax/test_concat.py @@ -7,26 +7,31 @@ fail_list = [ """ +@public def cat(i1: bytes <= 10, i2: bytes <= 30) -> bytes <= 40: return concat(i1, i2, i1, i1) """, """ +@public def cat(i1: bytes <= 10, i2: bytes <= 30) -> bytes <= 40: return concat(i1, 5) """, """ +@public def sandwich(inp: bytes <= 100, inp2: bytes32) -> bytes <= 163: return concat(inp2, inp, inp2) """, """ y: bytes <= 10 +@public def krazykonkat(z: bytes <= 10) -> bytes <= 24: x = "cow" self.y = "horse" return concat(x, " ", self.y, " ", z) """, """ +@public def cat_list(y: num) -> bytes <= 40: x = [y] return concat("test", y) @@ -35,24 +40,29 @@ def cat_list(y: num) -> bytes <= 40: valid_list = [ """ +@public def cat(i1: bytes <= 10, i2: bytes <= 30) -> bytes <= 40: return concat(i1, i2) """, """ +@public def cat(i1: bytes <= 10, i2: bytes <= 30) -> bytes <= 40: return concat(i1, i1, i1, i1) """, """ +@public def cat(i1: bytes <= 10, i2: bytes <= 30) -> bytes <= 40: return concat(i1, i1) """, """ +@public def sandwich(inp: bytes <= 100, inp2: bytes32) -> bytes <= 165: return concat(inp2, inp, inp2) """, """ y: bytes <= 10 +@public def krazykonkat(z: bytes <= 10) -> bytes <= 25: x = "cow" self.y = "horse" diff --git a/tests/parser/syntax/test_create_with_code_of.py b/tests/parser/syntax/test_create_with_code_of.py index 2c4aca3f35..222c0f86c1 100644 --- a/tests/parser/syntax/test_create_with_code_of.py +++ b/tests/parser/syntax/test_create_with_code_of.py @@ -6,6 +6,7 @@ fail_list = [ """ +@public def foo(): x = create_with_code_of(0x1234567890123456789012345678901234567890, value=4, value=9) """ @@ -20,14 +21,17 @@ def test_type_mismatch_exception(bad_code): valid_list = [ """ +@public def foo(): x = create_with_code_of(0x1234567890123456789012345678901234567890) """, """ +@public def foo(): x = create_with_code_of(0x1234567890123456789012345678901234567890, value=as_wei_value(9, wei)) """, """ +@public def foo(): x = create_with_code_of(0x1234567890123456789012345678901234567890, value=9) """ diff --git a/tests/parser/syntax/test_extract32.py b/tests/parser/syntax/test_extract32.py index 4aed6bc639..6df1dc7321 100644 --- a/tests/parser/syntax/test_extract32.py +++ b/tests/parser/syntax/test_extract32.py @@ -6,6 +6,7 @@ fail_list = [ """ +@public def foo() -> num256: return extract32("cowcowcowcowcowccowcowcowcowcowccowcowcowcowcowccowcowcowcowcowc", 0) """ @@ -21,17 +22,20 @@ def test_extract32_fail(bad_code): valid_list = [ """ +@public def foo() -> num256: return extract32("cowcowcowcowcowccowcowcowcowcowccowcowcowcowcowccowcowcowcowcowc", 0, type=num256) """, """ x: bytes <= 100 +@public def foo() -> num256: self.x = "cowcowcowcowcowccowcowcowcowcowccowcowcowcowcowccowcowcowcowcowc" return extract32(self.x, 0, type=num256) """, """ x: bytes <= 100 +@public def foo() -> num256: self.x = "cowcowcowcowcowccowcowcowcowcowccowcowcowcowcowccowcowcowcowcowc" return extract32(self.x, 1, type=num256) diff --git a/tests/parser/syntax/test_for_range.py b/tests/parser/syntax/test_for_range.py index 93714b629f..61ae502323 100644 --- a/tests/parser/syntax/test_for_range.py +++ b/tests/parser/syntax/test_for_range.py @@ -4,16 +4,19 @@ valid_list = [ """ +@public def foo(): for i in range(10): pass """, """ +@public def foo(): for i in range(10, 20): pass """, """ +@public def foo(): x = 5 for i in range(x, x + 10): diff --git a/tests/parser/syntax/test_invalids.py b/tests/parser/syntax/test_invalids.py index 5d40a1572a..945d30da7b 100644 --- a/tests/parser/syntax/test_invalids.py +++ b/tests/parser/syntax/test_invalids.py @@ -29,33 +29,39 @@ def must_succeed(code): """) must_succeed(""" +@public def foo(x: num): pass """) must_succeed(""" +@public def foo(): x: num x = 5 """) must_succeed(""" +@public def foo(): x = 5 """) must_fail(""" +@public def foo(): x = 5 x = 0x1234567890123456789012345678901234567890 """, TypeMismatchException) must_fail(""" +@public def foo(): x = 5 x = 3.5 """, TypeMismatchException) must_succeed(""" +@public def foo(): x = 5 x = 3 @@ -63,71 +69,83 @@ def foo(): must_succeed(""" b: num +@public def foo(): self.b = 7 """) must_fail(""" b: num +@public def foo(): self.b = 7.5 """, TypeMismatchException) must_succeed(""" b: decimal +@public def foo(): self.b = 7.5 """) must_succeed(""" b: decimal +@public def foo(): self.b = 7 """) must_fail(""" b: num[5] +@public def foo(): self.b = 7 """, TypeMismatchException) must_succeed(""" b: num[num] +@public def foo(): x = self.b[5] """) must_fail(""" b: num[num] +@public def foo(): x = self.b[5.7] """, TypeMismatchException) must_succeed(""" b: num[decimal] +@public def foo(): x = self.b[5] """) must_fail(""" b: num[num] +@public def foo(): self.b[3] = 5.6 """, TypeMismatchException) must_succeed(""" b: num[num] +@public def foo(): self.b[3] = -5 """) must_succeed(""" b: num[num] +@public def foo(): self.b[-3] = 5 """) must_succeed(""" +@public def foo(): x: num[5] z = x[2] @@ -135,6 +153,7 @@ def foo(): must_succeed(""" x: num +@public def foo() -> num: self.x = 5 """) @@ -148,60 +167,72 @@ def foo() -> num: must_fail(""" foo: num[3] +@public def foo(): self.foo = 5 """, TypeMismatchException) must_succeed(""" foo: num[3] +@public def foo(): self.foo[0] = 5 """) must_fail(""" +@public def foo() -> address: return as_unitless_number([1, 2, 3]) """, TypeMismatchException) must_succeed(""" +@public def foo(x: wei_value, y: currency_value, z: num (wei*currency/sec**2)) -> num (sec**2): return x * y / z """) must_fail(""" +@public def baa() -> decimal: return 2.0**2 """, TypeMismatchException) must_succeed(""" +@public def foo(): throw """) must_succeed(""" +@public def foo(): pass +@public def goo(): self.foo() """) must_succeed(""" +@public def foo(): MOOSE = 45 """) must_fail(""" +@public def foo(): x = -self """, TypeMismatchException) must_fail(""" +@public def foo() -> num: return """, TypeMismatchException) must_fail(""" +@public def foo(): return 3 """, TypeMismatchException) diff --git a/tests/parser/syntax/test_len.py b/tests/parser/syntax/test_len.py index 2fb2900fe5..de6786f4a2 100644 --- a/tests/parser/syntax/test_len.py +++ b/tests/parser/syntax/test_len.py @@ -7,10 +7,12 @@ fail_list = [ """ +@public def foo(inp: num) -> num: return len(inp) """, """ +@public def foo(inp: num) -> address: return len(inp) """ @@ -30,6 +32,7 @@ def test_block_fail(bad_code): valid_list = [ """ +@public def foo(inp: bytes <= 10) -> num: return len(inp) """ diff --git a/tests/parser/syntax/test_list.py b/tests/parser/syntax/test_list.py index 01509b7136..f4243bb524 100644 --- a/tests/parser/syntax/test_list.py +++ b/tests/parser/syntax/test_list.py @@ -7,88 +7,105 @@ fail_list = [ """ +@public def foo(): x = [1, 2, 3] x = 4 """, """ +@public def foo(): x = [1, 2, 3] x = [4, 5, 6, 7] """, """ +@public def foo() -> num[2]: return [3,5,7] """, """ +@public def foo() -> num[2]: return [3] """, """ y: num[3] +@public def foo(x: num[3]) -> num: self.y = x[0] """, """ y: num[3] +@public def foo(x: num[3]) -> num: self.y[0] = x """, """ y: num[4] +@public def foo(x: num[3]) -> num: self.y = x """, """ foo: num[3] +@public def foo(): self.foo = [1, 2, 0x1234567890123456789012345678901234567890] """, (""" foo: num[3] +@public def foo(): self.foo = [] """, StructureException), """ b: num[5] +@public def foo(): x = self.b[0][1] """, """ foo: num[3] +@public def foo(): self.foo = [1, [2], 3] """, """ bar: num[3][3] +@public def foo(): self.bar = 5 """, """ bar: num[3][3] +@public def foo(): self.bar = [2, 5] """, """ foo: num[3] +@public def foo(): self.foo = [1, 2, 3, 4] """, """ foo: num[3] +@public def foo(): self.foo = [1, 2] """, """ b: num[5] +@public def foo(): self.b[0] = 7.5 """, """ b: num[5] +@public def foo(): x = self.b[0].cow """, @@ -108,56 +125,67 @@ def test_block_fail(bad_code): valid_list = [ """ +@public def foo(): x = [1, 2, 3] x = [4, 5, 6] """, """ +@public def foo() -> num[2][2]: return [[1,2],[3,4]] """, """ +@public def foo() -> decimal[2][2]: return [[1,2],[3,4]] """, """ +@public def foo() -> decimal[2][2]: return [[1,2.0],[3.5,4]] """, """ +@public def foo(x: num[3]) -> num: return x[0] """, """ y: num[3] +@public def foo(x: num[3]) -> num: self.y = x """, """ y: decimal[3] +@public def foo(x: num[3]) -> num: self.y = x """, """ y: decimal[2][2] +@public def foo(x: num[2][2]) -> num: self.y = x """, """ y: decimal[2] +@public def foo(x: num[2][2]) -> num: self.y = x[1] """, """ +@public def foo() -> num[2]: return [3,5] """, """ foo: decimal[3] +@public def foo(): self.foo = [1, 2.1, 3] """, @@ -166,17 +194,20 @@ def foo(): """, """ foo: num[3] +@public def foo(): self.foo = [1, 2, 3] """, """ b: num[5] +@public def foo(): a: num[5] self.b[0] = a[0] """, """ b: decimal[5] +@public def foo(): self.b[0] = 7 """ diff --git a/tests/parser/syntax/test_maps.py b/tests/parser/syntax/test_maps.py index 6014762029..9903858e68 100644 --- a/tests/parser/syntax/test_maps.py +++ b/tests/parser/syntax/test_maps.py @@ -9,98 +9,116 @@ """ mom: {a: {c: num}[3], b: num} nom: {a: {c: num}[2], b: num} +@public def foo(): self.nom = self.mom """, """ mom: {a: {c: num}[3], b: num} nom: {a: {c: decimal}[2], b: num} +@public def foo(): self.nom = self.mom """, """ mom: {a: {c: num}[3], b: num} nom: {a: {c: num}[3], b: num, c: num} +@public def foo(): self.nom = self.mom """, """ mom: {a: {c: num}[3], b: num} nom: {a: {c: num}[3]} +@public def foo(): self.nom = self.mom """, """ mom: {a: {c: num}[3], b: num} nom: {a: {c: num}, b: num} +@public def foo(): self.nom = self.mom """, """ mom: {a: {c: num}[3], b: num} nom: {c: num}[3] +@public def foo(): self.nom = self.mom.b """, """ mom: {a: {c: num}[3], b: num} nom: {c: num}[3] +@public def foo(): self.mom = {a: self.nom, b: 5.5} """, """ mom: {a: {c: num}[3], b: num} nom: {c: decimal}[3] +@public def foo(): self.mom = {a: self.nom, b: 5} """, """ mom: {a: {c: num}[3], b: num} nom: {c: num}[3] +@public def foo(): self.mom = {a: self.nom, b: self.nom} """, """ nom: {a: {c: num}[num], b: num} +@public def foo(): self.nom = None """, """ nom: {a: {c: num}[num], b: num} +@public def foo(): self.nom = {a: [{c: 5}], b: 7} """, """ foo: num[3] +@public def foo(): self.foo = {0: 5, 1: 7, 2: 9} """, """ foo: num[3] +@public def foo(): self.foo = {a: 5, b: 7, c: 9} """, """ +@public def foo() -> num: return {cow: 5, dog: 7} """, """ x: {cow: num, cor: num} +@public def foo(): self.x.cof = 1 """, """ b: {foo: num} +@public def foo(): self.b = {foo: 1, foo: 2} """, """ b: {foo: num, bar: num} +@public def foo(): x = self.b.cow """, """ b: {foo: num, bar: num} +@public def foo(): x = self.b[0] """, @@ -117,23 +135,27 @@ def test_block_fail(bad_code): """ mom: {a: {c: num}[3], b: num} nom: {a: {c: decimal}[3], b: num} +@public def foo(): self.nom = self.mom """, """ mom: {a: {c: num}[3], b: num} nom: {c: num}[3] +@public def foo(): self.nom = self.mom.a """, """ nom: {a: {c: num}[num], b: num} +@public def foo(): self.nom.a[135] = {c: 6} self.nom.b = 9 """, """ nom: {c: num}[3] +@public def foo(): mom: {a: {c: num}[3], b: num} mom.a = self.nom @@ -141,17 +163,20 @@ def foo(): """ mom: {a: {c: num}[3], b: num} nom: {c: num}[3] +@public def foo(): self.mom = {a: self.nom, b: 5} """, """ mom: {a: {c: num}[3], b: num} nom: {c: num}[3] +@public def foo(): self.mom = {a: null, b: 5} """, """ mom: {a: {c: num}[3], b: num} +@public def foo(): nom: {c: num}[3] self.mom = {a: nom, b: 5} @@ -159,11 +184,13 @@ def foo(): """ mom: {a: {c: decimal}[3], b: num} nom: {c: num}[3] +@public def foo(): self.mom = {a: self.nom, b: 5} """, """ b: {foo: num, bar: num} +@public def foo(): x = self.b.bar """, diff --git a/tests/parser/syntax/test_minmax.py b/tests/parser/syntax/test_minmax.py index 4ca0c3556f..9b61afb67c 100644 --- a/tests/parser/syntax/test_minmax.py +++ b/tests/parser/syntax/test_minmax.py @@ -7,10 +7,12 @@ fail_list = [ """ +@public def foo(): y = min(7, 0x1234567890123456789012345678901234567890) """, """ +@public def foo(): y = min(7, as_num256(3)) """ diff --git a/tests/parser/syntax/test_nested_list.py b/tests/parser/syntax/test_nested_list.py index 23f8b77339..430922ffd9 100644 --- a/tests/parser/syntax/test_nested_list.py +++ b/tests/parser/syntax/test_nested_list.py @@ -8,31 +8,37 @@ fail_list = [ """ bar: num[3][3] +@public def foo(): self.bar = [[1, 2], [3, 4, 5], [6, 7, 8]] """, """ bar: num[3][3] +@public def foo(): self.bar = [[1, 2, 3], [4, 5, 6], [7, 8, 9.0]] """, """ +@public def foo() -> num[2]: return [[1,2],[3,4]] """, """ +@public def foo() -> num[2][2]: return [1,2] """, """ y: address[2][2] +@public def foo(x: num[2][2]) -> num: self.y = x """, (""" bar: num[3][3] +@public def foo() -> num[3]: self.bar = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] for x in self.bar: @@ -56,11 +62,13 @@ def test_nested_list_fail(bad_code): valid_list = [ """ bar: num[3][3] +@public def foo(): self.bar = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] """, """ bar: decimal[3][3] +@public def foo(): self.bar = [[1, 2, 3], [4, 5, 6], [7, 8, 9.0]] """ diff --git a/tests/parser/syntax/test_public.py b/tests/parser/syntax/test_public.py index b033faa1b7..1b21955b08 100644 --- a/tests/parser/syntax/test_public.py +++ b/tests/parser/syntax/test_public.py @@ -12,6 +12,7 @@ y: public(num(wei / sec ** 2)) z: public(num(1 / sec)) +@public def foo() -> num(sec ** 2): return self.x / self.y / self.z """ diff --git a/tests/parser/syntax/test_raw_call.py b/tests/parser/syntax/test_raw_call.py index 67ce671b98..57c35e17ce 100644 --- a/tests/parser/syntax/test_raw_call.py +++ b/tests/parser/syntax/test_raw_call.py @@ -7,14 +7,17 @@ fail_list = [ (""" +@public def foo(): x = raw_call(0x1234567890123456789012345678901234567890, "cow", outsize=4, outsize=9) """, SyntaxError), """ +@public def foo(): raw_log(["cow"], "dog") """, """ +@public def foo(): raw_log([], 0x1234567890123456789012345678901234567890) """ @@ -34,14 +37,17 @@ def test_raw_call_fail(bad_code): valid_list = [ """ +@public def foo(): x = raw_call(0x1234567890123456789012345678901234567890, "cow", outsize=4, gas=595757) """, """ +@public def foo(): x = raw_call(0x1234567890123456789012345678901234567890, "cow", outsize=4, gas=595757, value=as_wei_value(9, wei)) """, """ +@public def foo(): x = raw_call(0x1234567890123456789012345678901234567890, "cow", outsize=4, gas=595757, value=9) """ diff --git a/tests/parser/syntax/test_return_tuple.py b/tests/parser/syntax/test_return_tuple.py index 4bcbef8941..d27e5d4339 100644 --- a/tests/parser/syntax/test_return_tuple.py +++ b/tests/parser/syntax/test_return_tuple.py @@ -7,6 +7,7 @@ fail_list = [ """ +@public def unmatched_tupl_length() -> (bytes <= 8, num, bytes <= 8): return "test", 123 """ diff --git a/tests/parser/syntax/test_rlplist.py b/tests/parser/syntax/test_rlplist.py index fa0376f2f5..bfe659cd10 100644 --- a/tests/parser/syntax/test_rlplist.py +++ b/tests/parser/syntax/test_rlplist.py @@ -7,26 +7,31 @@ fail_list = [ """ +@public def foo() -> address: x = RLPList('\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', [address, bytes32]) return x[1] """, """ +@public def foo() -> address: x = RLPList('\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', [address, bytes32]) return x[2] """, """ +@public def foo() -> bytes <= 500: x = RLPList('\xe1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', [bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes]) return x[1] """, (""" +@public def foo() -> bytes <= 500: x = 1 return RLPList('\xe0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') """, StructureException), """ +@public def foo() -> bytes <= 500: x = [1, 2, 3] return RLPList(x, [bytes]) @@ -47,16 +52,19 @@ def test_rlplist_fail(bad_code): valid_list = [ """ +@public def foo() -> bytes <= 500: x = RLPList('\xe0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', [bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes, bytes]) return x[1] """, """ +@public def foo() -> address: x = RLPList('\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', [address, bytes32]) return x[0] """, """ +@public def foo() -> bytes32: x = RLPList('\xf6\x9455555555555555555555\xa0GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', [address, bytes32]) return x[1] diff --git a/tests/parser/syntax/test_selfdestruct.py b/tests/parser/syntax/test_selfdestruct.py index 8abdce7525..6e6715d5f4 100644 --- a/tests/parser/syntax/test_selfdestruct.py +++ b/tests/parser/syntax/test_selfdestruct.py @@ -7,6 +7,7 @@ fail_list = [ """ +@public def foo(): selfdestruct(7) """ @@ -22,6 +23,7 @@ def test_block_fail(bad_code): valid_list = [ """ +@public def foo(): selfdestruct(0x1234567890123456789012345678901234567890) """ diff --git a/tests/parser/syntax/test_send.py b/tests/parser/syntax/test_send.py index 0a1ef23d33..8306c7a696 100644 --- a/tests/parser/syntax/test_send.py +++ b/tests/parser/syntax/test_send.py @@ -7,36 +7,43 @@ fail_list = [ """ +@public def foo(): send(1, 2) """, """ +@public def foo(): send(1, 2) """, """ +@public def foo(): send(0x1234567890123456789012345678901234567890, 2.5) """, """ +@public def foo(): send(0x1234567890123456789012345678901234567890, 0x1234567890123456789012345678901234567890) """, """ x: num +@public def foo(): send(0x1234567890123456789012345678901234567890, self.x) """, """ x: wei_value +@public def foo(): send(0x1234567890123456789012345678901234567890, self.x + 1.5) """, """ x: decimal +@public def foo(): send(0x1234567890123456789012345678901234567890, self.x) """ @@ -53,22 +60,26 @@ def test_send_fail(bad_code): """ x: wei_value +@public def foo(): send(0x1234567890123456789012345678901234567890, self.x + 1) """, """ x: decimal +@public def foo(): send(0x1234567890123456789012345678901234567890, as_wei_value(floor(self.x), wei)) """, """ x: wei_value +@public def foo(): send(0x1234567890123456789012345678901234567890, self.x) """, """ +@public def foo(): send(0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe, 5) """ diff --git a/tests/parser/syntax/test_sha3.py b/tests/parser/syntax/test_sha3.py index 0b95a695de..0c73b1a719 100644 --- a/tests/parser/syntax/test_sha3.py +++ b/tests/parser/syntax/test_sha3.py @@ -7,6 +7,7 @@ fail_list = [ """ +@public def foo(): x = sha3(3) """ @@ -21,10 +22,12 @@ def test_block_fail(bad_code): valid_list = [ """ +@public def foo(): x = sha3("moose") """, """ +@public def foo(): x = sha3(0x1234567890123456789012345678901234567890123456789012345678901234) """ diff --git a/tests/parser/syntax/test_slice.py b/tests/parser/syntax/test_slice.py index c8ea6d9302..e78a1d16d5 100644 --- a/tests/parser/syntax/test_slice.py +++ b/tests/parser/syntax/test_slice.py @@ -7,14 +7,17 @@ fail_list = [ """ +@public def foo(inp: bytes <= 10) -> bytes <= 2: return slice(inp, start=2, len=3) """, """ +@public def foo(inp: num) -> bytes <= 3: return slice(inp, start=2, len=3) """, """ +@public def foo(inp: bytes <= 10) -> bytes <= 3: return slice(inp, start=4.0, len=3) """ @@ -30,14 +33,17 @@ def test_slice_fail(bad_code): valid_list = [ """ +@public def foo(inp: bytes <= 10) -> bytes <= 3: return slice(inp, start=2, len=3) """, """ +@public def foo(inp: bytes <= 10) -> bytes <= 4: return slice(inp, start=2, len=3) """, """ +@public def foo() -> bytes <= 10: return slice("badmintonzzz", start=1, len=10) """ diff --git a/tests/parser/syntax/test_timestamp_timedelta.py b/tests/parser/syntax/test_timestamp_timedelta.py index b3ff342a91..32a68dabc6 100644 --- a/tests/parser/syntax/test_timestamp_timedelta.py +++ b/tests/parser/syntax/test_timestamp_timedelta.py @@ -7,44 +7,54 @@ fail_list = [ """ +@public def foo(x: timestamp) -> num: return x """, """ +@public def foo(x: timestamp) -> timedelta: return x """, """ +@public def foo(x: timestamp, y: timedelta) -> bool: return y < x """, """ +@public def foo(x: timestamp, y: timedelta) -> timedelta: return x + y """, """ +@public def foo(x: timestamp, y: timestamp) -> timestamp: return x + y """, """ +@public def foo(x: timestamp) -> timestamp: return x * 2 """, """ +@public def foo(x: timedelta, y: timedelta) -> timedelta: return x * y """, """ +@public def foo() -> timestamp: x = 30 y: timestamp return x + y """, """ +@public def foo(x: timedelta, y: num (wei/sec)) -> num: return x * y """, """ +@public def foo(x: timestamp, y: num (wei/sec)) -> wei_value: return x * y """ @@ -59,91 +69,111 @@ def test_timestamp_fail(bad_code): valid_list = [ """ +@public def foo(x: timestamp) -> timestamp: return x + 50 """, """ +@public def foo() -> timestamp: return 720 """, """ +@public def foo() -> timedelta: return 720 """, """ +@public def foo(x: timestamp, y: timedelta) -> timestamp: return x + y """, """ +@public def foo(x: timestamp, y: timestamp) -> bool: return y > x """, """ +@public def foo(x: timedelta, y: timedelta) -> bool: return y == x """, """ +@public def foo(x: timestamp) -> timestamp: return x """, """ +@public @constant def foo(x: timestamp) -> num: return 5 """, """ +@public @constant def foo(x: timestamp) -> timestamp: return x """, """ +@public def foo(x: timestamp) -> timestamp: y = x return y """, """ +@public def foo(x: timedelta) -> bool: return x > 50 """, """ +@public def foo(x: timestamp) -> bool: return x > 12894712 """, """ +@public def foo() -> timestamp: x: timestamp x = 30 return x """, """ +@public def foo(x: timestamp, y: timestamp) -> timedelta: return x - y """, """ +@public def foo(x: timedelta, y: timedelta) -> timedelta: return x + y """, """ +@public def foo(x: timedelta) -> timedelta: return x * 2 """, """ +@public def foo(x: timedelta, y: num (wei/sec)) -> wei_value: return x * y """, """ +@public def foo(x: num(sec, positional)) -> timestamp: return x """, """ x: timedelta +@public def foo() -> num(sec): return self.x """, """ x: timedelta y: num +@public @constant def foo() -> num(sec): return self.x @@ -151,6 +181,7 @@ def foo() -> num(sec): """ x: timedelta y: num +@public def foo() -> num(sec): self.y = 9 return 5 diff --git a/tests/parser/types/numbers/test_decimals.py b/tests/parser/types/numbers/test_decimals.py index 5e9bb9dbe9..7c86410508 100644 --- a/tests/parser/types/numbers/test_decimals.py +++ b/tests/parser/types/numbers/test_decimals.py @@ -5,46 +5,60 @@ def test_decimal_test(): decimal_test = """ +@public def foo() -> num: return(floor(999.0)) +@public def fop() -> num: return(floor(333.0 + 666.0)) +@public def foq() -> num: return(floor(1332.1 - 333.1)) +@public def bar() -> num: return(floor(27.0 * 37.0)) +@public def baz() -> num: x = 27.0 return(floor(x * 37.0)) +@public def baffle() -> num: return(floor(27.0 * 37)) +@public def mok() -> num: return(floor(999999.0 / 7.0 / 11.0 / 13.0)) +@public def mol() -> num: return(floor(499.5 / 0.5)) +@public def mom() -> num: return(floor(1498.5 / 1.5)) +@public def mon() -> num: return(floor(2997.0 / 3)) +@public def moo() -> num: return(floor(2997 / 3.0)) +@public def foom() -> num: return(floor(1999.0 % 1000.0)) +@public def foon() -> num: return(floor(1999.0 % 1000)) +@public def foop() -> num: return(floor(1999 % 1000.0)) """ @@ -73,25 +87,30 @@ def foop() -> num: def test_harder_decimal_test(): harder_decimal_test = """ +@public def phooey(inp: decimal) -> decimal: x = 10000.0 for i in range(4): x = x * inp return x +@public def arg(inp: decimal) -> decimal: return inp +@public def garg() -> decimal: x = 4.5 x *= 1.5 return x +@public def harg() -> decimal: x = 4.5 x *= 2 return x +@public def iarg() -> wei_value: x = as_wei_value(7, wei) x *= 2 diff --git a/tests/parser/types/numbers/test_num.py b/tests/parser/types/numbers/test_num.py index 0832da2b9e..7c266565e2 100644 --- a/tests/parser/types/numbers/test_num.py +++ b/tests/parser/types/numbers/test_num.py @@ -4,6 +4,7 @@ def test_exponents_with_nums(): exp_code = """ +@public def _num_exp(x: num, y: num) -> num: return x**y """ diff --git a/tests/parser/types/numbers/test_num256.py b/tests/parser/types/numbers/test_num256.py index d28741268a..024df07f31 100644 --- a/tests/parser/types/numbers/test_num256.py +++ b/tests/parser/types/numbers/test_num256.py @@ -6,27 +6,35 @@ def test_num256_code(assert_tx_failed): num256_code = """ +@public def _num256_add(x: num256, y: num256) -> num256: return num256_add(x, y) +@public def _num256_sub(x: num256, y: num256) -> num256: return num256_sub(x, y) +@public def _num256_mul(x: num256, y: num256) -> num256: return num256_mul(x, y) +@public def _num256_div(x: num256, y: num256) -> num256: return num256_div(x, y) +@public def _num256_gt(x: num256, y: num256) -> bool: return num256_gt(x, y) +@public def _num256_ge(x: num256, y: num256) -> bool: return num256_ge(x, y) +@public def _num256_lt(x: num256, y: num256) -> bool: return num256_lt(x, y) +@public def _num256_le(x: num256, y: num256) -> bool: return num256_le(x, y) """ @@ -69,12 +77,15 @@ def _num256_le(x: num256, y: num256) -> bool: def test_num256_mod(assert_tx_failed): num256_code = """ +@public def _num256_mod(x: num256, y: num256) -> num256: return num256_mod(x, y) +@public def _num256_addmod(x: num256, y: num256, z: num256) -> num256: return num256_addmod(x, y, z) +@public def _num256_mulmod(x: num256, y: num256, z: num256) -> num256: return num256_mulmod(x, y, z) """ @@ -96,6 +107,7 @@ def _num256_mulmod(x: num256, y: num256, z: num256) -> num256: def test_num256_with_exponents(assert_tx_failed): exp_code = """ +@public def _num256_exp(x: num256, y: num256) -> num256: return num256_exp(x,y) """ @@ -113,12 +125,15 @@ def _num256_exp(x: num256, y: num256) -> num256: def test_num256_to_num_casting(assert_tx_failed): code = """ +@public def _num256_to_num(x: num(num256)) -> num: return x +@public def _num256_to_num_call(x: num256) -> num: return self._num256_to_num(x) +@public def built_in_conversion(x: num256) -> num: return as_num128(x) """ @@ -145,6 +160,7 @@ def built_in_conversion(x: num256) -> num: def test_modmul(): modexper = """ +@public def exp(base: num256, exponent: num256, modulus: num256) -> num256: o = as_num256(1) for i in range(256): diff --git a/tests/parser/types/test_bytes.py b/tests/parser/types/test_bytes.py index 499830a088..55fff53254 100644 --- a/tests/parser/types/test_bytes.py +++ b/tests/parser/types/test_bytes.py @@ -5,6 +5,7 @@ def test_test_bytes(): test_bytes = """ +@public def foo(x: bytes <= 100) -> bytes <= 100: return x """ @@ -30,6 +31,7 @@ def foo(x: bytes <= 100) -> bytes <= 100: def test_test_bytes2(): test_bytes2 = """ +@public def foo(x: bytes <= 100) -> bytes <= 100: y = x return y @@ -51,24 +53,30 @@ def test_test_bytes3(): maa: bytes <= 60 y: num +@public def __init__(): self.x = 27 self.y = 37 +@public def set_maa(inp: bytes <= 60): self.maa = inp +@public def set_maa2(inp: bytes <= 60): ay = inp self.maa = ay +@public def get_maa() -> bytes <= 60: return self.maa +@public def get_maa2() -> bytes <= 60: ay = self.maa return ay +@public def get_xy() -> num: return self.x * self.y """ @@ -93,11 +101,13 @@ def get_xy() -> num: def test_test_bytes4(): test_bytes4 = """ a: bytes <= 60 +@public def foo(inp: bytes <= 60) -> bytes <= 60: self.a = inp self.a = None return self.a +@public def bar(inp: bytes <= 60) -> bytes <= 60: b = inp b = None @@ -115,23 +125,29 @@ def test_test_bytes5(): test_bytes5 = """ g: {a: bytes <= 50, b: bytes <= 50} +@public def foo(inp1: bytes <= 40, inp2: bytes <= 45): self.g = {a: inp1, b: inp2} +@public def check1() -> bytes <= 50: return self.g.a +@public def check2() -> bytes <= 50: return self.g.b +@public def bar(inp1: bytes <= 40, inp2: bytes <= 45) -> bytes <= 50: h = {a: inp1, b: inp2} return h.a +@public def bat(inp1: bytes <= 40, inp2: bytes <= 45) -> bytes <= 50: h = {a: inp1, b: inp2} return h.b +@public def quz(inp1: bytes <= 40, inp2: bytes <= 45): h = {a: inp1, b: inp2} self.g = h @@ -152,6 +168,7 @@ def quz(inp1: bytes <= 40, inp2: bytes <= 45): def test_bytes_to_num_code(): bytes_to_num_code = """ +@public def foo(x: bytes <= 32) -> num: return bytes_to_num(x) """ diff --git a/tests/parser/types/test_lists.py b/tests/parser/types/test_lists.py index 944d0f45f8..516774a7e2 100644 --- a/tests/parser/types/test_lists.py +++ b/tests/parser/types/test_lists.py @@ -9,25 +9,31 @@ def test_list_tester_code(): z2: num[2][2] z3: num[2] +@public def foo(x: num[3]) -> num: return x[0] + x[1] + x[2] +@public def goo(x: num[2][2]) -> num: return x[0][0] + x[0][1] + x[1][0] * 10 + x[1][1] * 10 +@public def hoo(x: num[3]) -> num: y = x return y[0] + x[1] + y[2] +@public def joo(x: num[2][2]) -> num: y = x y2 = x[1] return y[0][0] + y[0][1] + y2[0] * 10 + y2[1] * 10 +@public def koo(x: num[3]) -> num: self.z = x return self.z[0] + x[1] + self.z[2] +@public def loo(x: num[2][2]) -> num: self.z2 = x self.z3 = x[1] @@ -48,42 +54,53 @@ def test_list_output_tester_code(): list_output_tester_code = """ z: num[2] +@public def foo() -> num[2]: return [3, 5] +@public def goo() -> num[2]: x = [3, 5] return x +@public def hoo() -> num[2]: self.z = [3, 5] return self.z +@public def joo() -> num[2]: self.z = [3, 5] x = self.z return x +@public def koo() -> num[2][2]: return [[1,2],[3,4]] +@public def loo() -> num[2][2]: x = [[1,2],[3,4]] return x +@public def moo() -> num[2][2]: x = [1,2] return [x,[3,4]] +@public def noo(inp: num[2]) -> num[2]: return inp +@public def poo(inp: num[2][2]) -> num[2][2]: return inp +@public def qoo(inp: num[2]) -> num[2][2]: return [inp,[3,4]] +@public def roo(inp: num[2]) -> decimal[2][2]: return [inp,[3,4]] """ @@ -106,6 +123,7 @@ def roo(inp: num[2]) -> decimal[2][2]: def test_array_accessor(): array_accessor = """ +@public def test_array(x: num, y: num, z: num, w: num) -> num: a: num[4] a[0] = x @@ -122,6 +140,7 @@ def test_array(x: num, y: num, z: num, w: num) -> num: def test_two_d_array_accessor(): two_d_array_accessor = """ +@public def test_array(x: num, y: num, z: num, w: num) -> num: a: num[2][2] a[0][0] = x diff --git a/tests/parser/types/test_string_literal.py b/tests/parser/types/test_string_literal.py index e7e63ae96b..63791929d0 100644 --- a/tests/parser/types/test_string_literal.py +++ b/tests/parser/types/test_string_literal.py @@ -5,21 +5,27 @@ def test_string_literal_code(): string_literal_code = """ +@public def foo() -> bytes <= 5: return "horse" +@public def bar() -> bytes <= 10: return concat("b", "a", "d", "m", "i", "", "nton") +@public def baz() -> bytes <= 40: return concat("0123456789012345678901234567890", "12") +@public def baz2() -> bytes <= 40: return concat("01234567890123456789012345678901", "12") +@public def baz3() -> bytes <= 40: return concat("0123456789012345678901234567890", "1") +@public def baz4() -> bytes <= 100: return concat("01234567890123456789012345678901234567890123456789", "01234567890123456789012345678901234567890123456789") @@ -41,6 +47,7 @@ def test_string_literal_splicing_fuzz(): kode = """ moo: bytes <= 100 +@public def foo(s: num, L: num) -> bytes <= 100: x = 27 r = slice("%s", start=s, len=L) @@ -48,6 +55,7 @@ def foo(s: num, L: num) -> bytes <= 100: if x * y == 999: return r +@public def bar(s: num, L: num) -> bytes <= 100: self.moo = "%s" x = 27 @@ -56,6 +64,7 @@ def bar(s: num, L: num) -> bytes <= 100: if x * y == 999: return r +@public def baz(s: num, L: num) -> bytes <= 100: x = 27 self.moo = slice("%s", start=s, len=L) diff --git a/tests/parser/types/value/test_wei.py b/tests/parser/types/value/test_wei.py index 8f7eea93ea..9bbfb061bf 100644 --- a/tests/parser/types/value/test_wei.py +++ b/tests/parser/types/value/test_wei.py @@ -5,18 +5,23 @@ def test_test_wei(): test_wei = """ +@public def return_2_finney() -> wei_value: return as_wei_value(2, finney) +@public def return_3_finney() -> wei_value: return as_wei_value(2 + 1, finney) +@public def return_2p5_ether() -> wei_value: return as_wei_value(2.5, ether) +@public def return_3p5_ether() -> wei_value: return as_wei_value(2.5 + 1, ether) +@public def return_2pow64_wei() -> wei_value: return as_wei_value(18446744.073709551616, szabo) """ diff --git a/viper/function_signature.py b/viper/function_signature.py index 0a8138f128..57c3bf4db0 100644 --- a/viper/function_signature.py +++ b/viper/function_signature.py @@ -70,7 +70,7 @@ def from_definition(cls, code): pos += get_size_of_type(parsed_type) * 32 # Apply decorators - const, payable, internal = False, False, False + const, payable, internal, public = False, False, False, False for dec in code.decorator_list: if isinstance(dec, ast.Name) and dec.id == "constant": const = True @@ -78,8 +78,15 @@ def from_definition(cls, code): payable = True elif isinstance(dec, ast.Name) and dec.id == "internal": internal = True + elif isinstance(dec, ast.Name) and dec.id == "public": + public = True else: raise StructureException("Bad decorator", dec) + if public and internal: + raise StructureException("Cannot use public and internal decorators on the same function") + if not public and not internal and not isinstance(code.body[0], ast.Pass): + # import pdb; pdb.set_trace() + raise StructureException("Function visibility must be declared") # Determine the return type and whether or not it's constant. Expects something # of the form: # def foo(): ... diff --git a/viper/parser/parser.py b/viper/parser/parser.py index ffcbcde519..7ddcbb9bcf 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -155,7 +155,7 @@ def _mk_getter_helper(typ, depth=0): # Make a list of getters for a given variable name with a given type def mk_getter(varname, typ): funs = _mk_getter_helper(typ) - return ['@constant\ndef get_%s%s(%s) -> %s: return self.%s%s' % (varname, funname, head.rstrip(', '), base, varname, tail) + return ['@public\n@constant\ndef get_%s%s(%s) -> %s: return self.%s%s' % (varname, funname, head.rstrip(', '), base, varname, tail) for (funname, head, tail, base) in funs] From 4601445aebccddd47a163c950e9a2c3f429cf252 Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 14 Nov 2017 11:09:25 -0700 Subject: [PATCH 069/162] Add tests for public decorator --- .../parser/features/decorators/test_public.py | 28 +++++++++++++++++++ .../features/test_external_contract_calls.py | 3 ++ tests/parser/types/numbers/test_num.py | 11 +++++++- viper/function_signature.py | 1 - viper/parser/parser.py | 2 +- 5 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 tests/parser/features/decorators/test_public.py diff --git a/tests/parser/features/decorators/test_public.py b/tests/parser/features/decorators/test_public.py new file mode 100644 index 0000000000..9739d2c0cb --- /dev/null +++ b/tests/parser/features/decorators/test_public.py @@ -0,0 +1,28 @@ +import pytest +from viper.exceptions import StructureException + +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation, get_contract + + +def test_invalid_if_both_public_and_internal(assert_compile_failed): + code = """ +@public +@internal +def foo(): + x = 1 +""" + + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) + + +def test_invalid_if_visibility_isnt_declared(assert_compile_failed): + code = """ +def foo(): + x = 1 +""" + + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) + + + diff --git a/tests/parser/features/test_external_contract_calls.py b/tests/parser/features/test_external_contract_calls.py index 17ba007c7e..d5bc1aadbe 100644 --- a/tests/parser/features/test_external_contract_calls.py +++ b/tests/parser/features/test_external_contract_calls.py @@ -115,6 +115,7 @@ def test_constant_external_contract_call_cannot_change_state(assert_tx_failed): contract_1 = """ lucky: public(num) +@public def set_lucky(_lucky: num) -> num: self.lucky = _lucky return _lucky @@ -127,10 +128,12 @@ def set_lucky(_lucky: num) -> num: class Foo(): def set_lucky(_lucky: num) -> num: pass +@public @constant def set_lucky_expr(arg1: address, arg2: num): Foo(arg1).set_lucky(arg2) +@public @constant def set_lucky_stmt(arg1: address, arg2: num) -> num: return Foo(arg1).set_lucky(arg2) diff --git a/tests/parser/types/numbers/test_num.py b/tests/parser/types/numbers/test_num.py index 7c266565e2..0d4bf92031 100644 --- a/tests/parser/types/numbers/test_num.py +++ b/tests/parser/types/numbers/test_num.py @@ -17,10 +17,13 @@ def _num_exp(x: num, y: num) -> num: assert c._num_exp(3,3) == 27 assert c._num_exp(72,19) == 72**19 -def test_nagative_nums(assert_tx_failed): +def test_negative_nums(assert_tx_failed): negative_nums_code = """ +@public def _negative_num() -> num: return -1 + +@public def _negative_exp() -> num: return -(1+2) """ @@ -32,21 +35,27 @@ def _negative_exp() -> num: def test_num_bound(assert_tx_failed): num_bound_code = """ +@public def _num(x: num) -> num: return x +@public def _num_add(x: num, y: num) -> num: return x + y +@public def _num_sub(x: num, y: num) -> num: return x - y +@public def _num_add3(x: num, y: num, z: num) -> num: return x + y + z +@public def _num_max() -> num: return 170141183460469231731687303715884105727 # 2**127 - 1 +@public def _num_min() -> num: return -170141183460469231731687303715884105728 # -2**127 """ diff --git a/viper/function_signature.py b/viper/function_signature.py index 57c3bf4db0..edeefcea3e 100644 --- a/viper/function_signature.py +++ b/viper/function_signature.py @@ -85,7 +85,6 @@ def from_definition(cls, code): if public and internal: raise StructureException("Cannot use public and internal decorators on the same function") if not public and not internal and not isinstance(code.body[0], ast.Pass): - # import pdb; pdb.set_trace() raise StructureException("Function visibility must be declared") # Determine the return type and whether or not it's constant. Expects something # of the form: diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 7ddcbb9bcf..8eee544746 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -155,7 +155,7 @@ def _mk_getter_helper(typ, depth=0): # Make a list of getters for a given variable name with a given type def mk_getter(varname, typ): funs = _mk_getter_helper(typ) - return ['@public\n@constant\ndef get_%s%s(%s) -> %s: return self.%s%s' % (varname, funname, head.rstrip(', '), base, varname, tail) + return ["""@public\n@constant\ndef get_%s%s(%s) -> %s: return self.%s%s""" % (varname, funname, head.rstrip(', '), base, varname, tail) for (funname, head, tail, base) in funs] From 35a915b41c27c7eff2c9fd13dd34800525842440 Mon Sep 17 00:00:00 2001 From: DavidKnott Date: Wed, 15 Nov 2017 21:40:40 -0700 Subject: [PATCH 070/162] Add public decorator to tests (#474) --- tests/parser/features/iteration/test_range_in.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/parser/features/iteration/test_range_in.py b/tests/parser/features/iteration/test_range_in.py index ba201e03cd..0343156497 100644 --- a/tests/parser/features/iteration/test_range_in.py +++ b/tests/parser/features/iteration/test_range_in.py @@ -82,13 +82,16 @@ def test_ownership(assert_tx_failed): owners: address[2] +@public def __init__(): self.owners[0] = msg.sender +@public def set_owner(i: num, new_owner: address): assert msg.sender in self.owners self.owners[i] = new_owner +@public def is_owner() -> bool: return msg.sender in self.owners """ From 494bd8103befafa620de38d2a71f2db6175658f1 Mon Sep 17 00:00:00 2001 From: Jesse B Miller Date: Mon, 20 Nov 2017 00:52:32 -0600 Subject: [PATCH 071/162] Document function visibility requirements --- docs/structure-of-a-contract.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index c54c4a3e03..0c3d7577e8 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -33,12 +33,13 @@ Functions are the executable units of code within a contract. :: + @public @payable - function bid(): // Function + def bid(): // Function // ... } :ref:`function-calls` can happen internally or externally and have different levels of visibility (:ref:`visibility-and-getters`) -towards other contracts. +towards other contracts. Functions must be decorated with either @public or @internal. From 358e30dd5c0cdcd6bff11cfe39bd74b4429935a2 Mon Sep 17 00:00:00 2001 From: Jesse B Miller Date: Mon, 20 Nov 2017 10:49:42 -0600 Subject: [PATCH 072/162] Clarifies expectation in error message This would have helped me when I saw this error learning Viper for the first time. --- viper/function_signature.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper/function_signature.py b/viper/function_signature.py index edeefcea3e..e31c16abd3 100644 --- a/viper/function_signature.py +++ b/viper/function_signature.py @@ -85,7 +85,7 @@ def from_definition(cls, code): if public and internal: raise StructureException("Cannot use public and internal decorators on the same function") if not public and not internal and not isinstance(code.body[0], ast.Pass): - raise StructureException("Function visibility must be declared") + raise StructureException("Function visibility must be declared (@public or @internal)") # Determine the return type and whether or not it's constant. Expects something # of the form: # def foo(): ... From 3d7c249efb21a2a316a638ee72c86a13de79848c Mon Sep 17 00:00:00 2001 From: Jesse B Miller Date: Mon, 20 Nov 2017 10:56:18 -0600 Subject: [PATCH 073/162] Note internal or public decorator requirement --- docs/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.rst b/docs/index.rst index f9038a3f79..f36b22be53 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,6 +48,7 @@ Following the principles and goals, Viper **does not** provide the following fea Compatibility-breaking Changelog ******************************** +* **2017.11.15**: Functions require either ``@internal`` or ``@public`` decorators. * **2017.07.25**: The ``def foo() -> num(const): ...`` syntax no longer works; you now need to do ``def foo() -> num: ...`` with a ``@constant`` decorator on the previous line. * **2017.07.25**: Functions without a ``@payable`` decorator now fail when called with nonzero wei. * **2017.07.25**: A function can only call functions that are declared above it (that is, A can call B only if B appears earlier in the code than A does). This was introduced From 4a31cba9a6e8d4a143484a6ecc1416d9c1ac9d5e Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Wed, 22 Nov 2017 13:43:17 +0200 Subject: [PATCH 074/162] Fixes test with for function visibility. --- tests/parser/features/test_bytes_map_keys.py | 13 +++++++++---- viper/parser/parser_utils.py | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/parser/features/test_bytes_map_keys.py b/tests/parser/features/test_bytes_map_keys.py index d9cb349c83..22a5f1509d 100644 --- a/tests/parser/features/test_bytes_map_keys.py +++ b/tests/parser/features/test_bytes_map_keys.py @@ -8,10 +8,11 @@ def test_basic_bytes_keys(): code = """ mapped_bytes: num[bytes <= 5] +@public def set(k: bytes <= 5, v: num): self.mapped_bytes[k] = v - +@public def get(k: bytes <= 5) -> num: return self.mapped_bytes[k] """ @@ -27,10 +28,11 @@ def test_basic_bytes_literal_key(): code = """ mapped_bytes: num[bytes <= 5] - +@public def set(v: num): self.mapped_bytes["test"] = v +@public def get(k: bytes <= 5) -> num: return self.mapped_bytes[k] """ @@ -46,10 +48,11 @@ def test_basic_long_bytes_as_keys(): code = """ mapped_bytes: num[bytes <= 34] +@public def set(k: bytes <= 34, v: num): self.mapped_bytes[k] = v - +@public def get(k: bytes <= 34) -> num: return self.mapped_bytes[k] """ @@ -65,10 +68,11 @@ def test_basic_very_long_bytes_as_keys(): code = """ mapped_bytes: num[bytes <= 4096] +@public def set(k: bytes <= 4096, v: num): self.mapped_bytes[k] = v - +@public def get(k: bytes <= 4096) -> num: return self.mapped_bytes[k] """ @@ -84,6 +88,7 @@ def test_mismatched_byte_length(): code = """ mapped_bytes: num[bytes <= 34] +@public def set(k: bytes <= 35, v: num): self.mapped_bytes[k] = v """ diff --git a/viper/parser/parser_utils.py b/viper/parser/parser_utils.py index ba60e1edc6..d54add66ef 100644 --- a/viper/parser/parser_utils.py +++ b/viper/parser/parser_utils.py @@ -20,7 +20,7 @@ get_size_of_type, ceil32 ) -from viper.utils import MemoryPositions, DECIMAL_DIVISOR, ceil32 +from viper.utils import MemoryPositions, DECIMAL_DIVISOR class NullAttractor(): From 1ac0824b24d8aec77a9720b1a4725affbdd822e2 Mon Sep 17 00:00:00 2001 From: Leo Arias Date: Wed, 22 Nov 2017 10:50:02 -0600 Subject: [PATCH 075/162] Add the install instructions for the Viper snap This requries #445, and enabling the continuous delivery on https://build.snapcraft.io --- docs/installing-viper.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/installing-viper.rst b/docs/installing-viper.rst index 4cb74030f8..d9a1711482 100644 --- a/docs/installing-viper.rst +++ b/docs/installing-viper.rst @@ -131,3 +131,14 @@ and try compiling a contract: :: make test viper examples/crowdfund.v.py + +**** +Snap +**** + +Viper is published in the snap store. In any of the `supported Linux distros `_, install it with: +:: + sudo snap install viper --edge + + +(Note that this is an experimental and unstable release, at the moment) From e9ad27481a1388d81bc238b53e00cc62021184e6 Mon Sep 17 00:00:00 2001 From: David Knott Date: Thu, 16 Nov 2017 15:16:40 -0700 Subject: [PATCH 076/162] Allow for gas estimation tests for constants --- .../features/decorators/test_constant.py | 17 ++++++++++++++++ .../features/iteration/test_for_in_list.py | 16 +++++++-------- .../features/iteration/test_range_in.py | 6 +++--- .../features/test_external_contract_calls.py | 16 +++++++-------- tests/parser/features/test_internal_call.py | 4 ++-- tests/parser/features/test_logging.py | 18 ++++++++--------- tests/parser/functions/test_raw_call.py | 3 ++- tests/parser/globals/test_getters.py | 2 +- tests/parser/integration/test_crowdfund.py | 13 ++++++------ tests/parser/types/numbers/test_num256.py | 2 +- tests/setup_transaction_tests.py | 20 ++++++++++++++++--- viper/parser/parser_utils.py | 3 +-- 12 files changed, 75 insertions(+), 45 deletions(-) create mode 100644 tests/parser/features/decorators/test_constant.py diff --git a/tests/parser/features/decorators/test_constant.py b/tests/parser/features/decorators/test_constant.py new file mode 100644 index 0000000000..5a78efc51d --- /dev/null +++ b/tests/parser/features/decorators/test_constant.py @@ -0,0 +1,17 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation_for_constants, get_contract + + +def test_constant_test(): + constant_test = """ +@public +@constant +def foo() -> num: + return 5 + """ + + c = get_contract_with_gas_estimation_for_constants(constant_test) + assert c.foo() == 5 + + print("Passed constant function test") diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py index b45a31c676..44a80bb684 100644 --- a/tests/parser/features/iteration/test_for_in_list.py +++ b/tests/parser/features/iteration/test_for_in_list.py @@ -15,7 +15,7 @@ def data() -> num: return -1 """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) assert c.data() == 3 @@ -30,7 +30,7 @@ def data() -> num: return -1 """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) assert c.data() == 7 @@ -51,7 +51,7 @@ def data() -> num: return -1 """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) assert c.data() == -1 assert c.set() is None @@ -75,7 +75,7 @@ def data() -> address: return 0x0000000000000000000000000000000000000000 """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) assert c.data() == "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" @@ -101,7 +101,7 @@ def iterate_return_second() -> address: return i """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) c.set(0, '0x82A978B3f5962A5b0957d9ee9eEf472EE55B42F1') c.set(1, '0x7d577a597B2742b498Cb5Cf0C26cDCD726d39E6e') @@ -131,7 +131,7 @@ def i_return(break_count: num) -> decimal: count += 1 """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) c.set(0, 0.0001) c.set(1, 1.1) @@ -156,7 +156,7 @@ def data() -> num: return -1 """ - assert_compile_failed(lambda: get_contract(code), StructureException) + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) def test_altering_list_within_for_loop_storage(assert_compile_failed): @@ -178,4 +178,4 @@ def data() -> num: return -1 """ - assert_compile_failed(lambda: get_contract(code), StructureException) + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) diff --git a/tests/parser/features/iteration/test_range_in.py b/tests/parser/features/iteration/test_range_in.py index 0343156497..8f1d2d08f2 100644 --- a/tests/parser/features/iteration/test_range_in.py +++ b/tests/parser/features/iteration/test_range_in.py @@ -15,7 +15,7 @@ def testin(x: num) -> bool: return False """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) assert c.testin(0) is True assert c.testin(1) is True @@ -38,7 +38,7 @@ def in_test(x: num) -> bool: return False """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) assert c.in_test(1) is True assert c.in_test(9) is True @@ -56,7 +56,7 @@ def in_test(x: num) -> bool: return False """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) assert c.in_test(1) is False assert c.in_test(-7) is False diff --git a/tests/parser/features/test_external_contract_calls.py b/tests/parser/features/test_external_contract_calls.py index d5bc1aadbe..7bc7274a58 100644 --- a/tests/parser/features/test_external_contract_calls.py +++ b/tests/parser/features/test_external_contract_calls.py @@ -10,7 +10,7 @@ def foo(arg1: num) -> num: return arg1 """ - c = get_contract(contract_1) + c = get_contract_with_gas_estimation(contract_1) contract_2 = """ class Foo(): @@ -20,7 +20,7 @@ def foo(arg1: num) -> num: pass def bar(arg1: address, arg2: num) -> num: return Foo(arg1).foo(arg2) """ - c2 = get_contract(contract_2) + c2 = get_contract_with_gas_estimation(contract_2) assert c2.bar(c.address, 1) == 1 print('Successfully executed an external contract call') @@ -44,7 +44,7 @@ def array() -> bytes <= 3: """ lucky_number = 7 - c = get_contract(contract_1, args=[lucky_number]) + c = get_contract_with_gas_estimation(contract_1, args=[lucky_number]) contract_2 = """ class Foo(): @@ -55,7 +55,7 @@ def array() -> bytes <= 3: pass def bar(arg1: address) -> num: return Foo(arg1).foo() """ - c2 = get_contract(contract_2) + c2 = get_contract_with_gas_estimation(contract_2) assert c2.bar(c.address) == lucky_number print('Successfully executed a complicated external contract call') @@ -68,7 +68,7 @@ def array() -> bytes <= 3: return 'dog' """ - c = get_contract(contract_1) + c = get_contract_with_gas_estimation(contract_1) contract_2 = """ class Foo(): @@ -79,7 +79,7 @@ def get_array(arg1: address) -> bytes <= 3: return Foo(arg1).array() """ - c2 = get_contract(contract_2) + c2 = get_contract_with_gas_estimation(contract_2) assert c2.get_array(c.address) == b'dog' @@ -122,7 +122,7 @@ def set_lucky(_lucky: num) -> num: """ lucky_number = 7 - c = get_contract(contract_1) + c = get_contract_with_gas_estimation(contract_1) contract_2 = """ class Foo(): @@ -138,7 +138,7 @@ def set_lucky_expr(arg1: address, arg2: num): def set_lucky_stmt(arg1: address, arg2: num) -> num: return Foo(arg1).set_lucky(arg2) """ - c2 = get_contract(contract_2) + c2 = get_contract_with_gas_estimation(contract_2) assert_tx_failed(lambda: c2.set_lucky_expr(c.address, lucky_number)) assert_tx_failed(lambda: c2.set_lucky_stmt(c.address, lucky_number)) diff --git a/tests/parser/features/test_internal_call.py b/tests/parser/features/test_internal_call.py index caa9e23430..1baa345feb 100644 --- a/tests/parser/features/test_internal_call.py +++ b/tests/parser/features/test_internal_call.py @@ -14,7 +14,7 @@ def bar() -> num: return self.foo() """ - c = get_contract(selfcall_code) + c = get_contract_with_gas_estimation(selfcall_code) assert c.bar() == 3 print("Passed no-argument self-call test") @@ -65,7 +65,7 @@ def returnten() -> num: return self._len("badminton!") """ - c = get_contract(selfcall_code_3) + c = get_contract_with_gas_estimation(selfcall_code_3) assert c.return_hash_of_cow_x_30() == u.sha3(b'cow' * 30) assert c.returnten() == 10 diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index 1ee7e8a90a..c3394dbaee 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -88,7 +88,7 @@ def bar(): log.MyLog(1, self) """ - c = get_contract(loggy_code) + c = get_contract_with_gas_estimation(loggy_code) c.foo() c.bar() logs = s.head_state.receipts[-1].logs[-1] @@ -171,7 +171,7 @@ def test_logging_with_input_bytes(bytes_helper): def foo(arg1: bytes <= 29, arg2: bytes <= 31): log.MyLog('bar', arg1, arg2) """ - c = get_contract(loggy_code) + c = get_contract_with_gas_estimation(loggy_code) c.foo('bar', 'foo') logs = s.head_state.receipts[-1].logs[-1] event_id = u.bytes_to_int(u.sha3(bytes('MyLog(bytes4,bytes29,bytes31)', 'utf-8'))) @@ -271,7 +271,7 @@ def foo_(): log.MyLog('yo') """ t.s = s - assert_tx_failed(lambda: get_contract(loggy_code), TypeMismatchException) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) def test_fails_when_topic_is_the_wrong_size(assert_tx_failed): @@ -283,7 +283,7 @@ def foo(): log.MyLog('bars') """ t.s = s - assert_tx_failed(lambda: get_contract(loggy_code), TypeMismatchException) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) def test_fails_when_input_topic_is_the_wrong_size(assert_tx_failed): @@ -295,7 +295,7 @@ def foo(arg1: bytes <= 4): log.MyLog(arg1) """ t.s = s - assert_tx_failed(lambda: get_contract(loggy_code), TypeMismatchException) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) def test_fails_when_data_is_the_wrong_size(assert_tx_failed): @@ -307,7 +307,7 @@ def foo(): log.MyLog('bars') """ t.s = s - assert_tx_failed(lambda: get_contract(loggy_code), TypeMismatchException) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) def test_fails_when_input_data_is_the_wrong_size(assert_tx_failed): @@ -319,7 +319,7 @@ def foo(arg1: bytes <= 4): log.MyLog(arg1) """ t.s = s - assert_tx_failed(lambda: get_contract(loggy_code), TypeMismatchException) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) def test_fails_when_log_data_is_over_32_bytes(assert_tx_failed): @@ -331,7 +331,7 @@ def foo(): pass """ t.s = s - assert_tx_failed(lambda: get_contract(loggy_code), VariableDeclarationException) + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) def test_logging_fails_with_over_three_topics(assert_tx_failed): @@ -437,7 +437,7 @@ def ioo(inp: bytes <= 100): raw_log([], inp) """ - c = get_contract(loggy_code) + c = get_contract_with_gas_estimation(loggy_code) c.foo() assert s.head_state.receipts[-1].logs[0].data == b'moo' c.goo() diff --git a/tests/parser/functions/test_raw_call.py b/tests/parser/functions/test_raw_call.py index 350e2f6e82..96be756d27 100644 --- a/tests/parser/functions/test_raw_call.py +++ b/tests/parser/functions/test_raw_call.py @@ -45,7 +45,8 @@ def create_and_call_returnten(inp: address) -> num: @public def create_and_return_forwarder(inp: address) -> address: - return create_with_code_of(inp) + x = create_with_code_of(inp) + return x """ c2 = get_contract(outer_code) diff --git a/tests/parser/globals/test_getters.py b/tests/parser/globals/test_getters.py index f5114b1f84..5ae5347a41 100644 --- a/tests/parser/globals/test_getters.py +++ b/tests/parser/globals/test_getters.py @@ -17,7 +17,7 @@ def foo() -> num: """ - c = get_contract_with_gas_estimation(state_accessor) + c = get_contract(state_accessor) c.oo() assert c.foo() == 5 print('Passed basic state accessor test') diff --git a/tests/parser/integration/test_crowdfund.py b/tests/parser/integration/test_crowdfund.py index dd57ad4963..042246f589 100644 --- a/tests/parser/integration/test_crowdfund.py +++ b/tests/parser/integration/test_crowdfund.py @@ -1,6 +1,6 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract + get_contract_with_gas_estimation_for_constants def test_crowdfund(): @@ -24,7 +24,7 @@ def __init__(_beneficiary: address, _goal: wei_value, _timelimit: timedelta): @public @payable def participate(): - assert block.timestamp < self.deadline + # assert block.timestamp < self.deadline nfi = self.nextFunderIndex self.funders[nfi].sender = msg.sender self.funders[nfi].value = msg.value @@ -74,8 +74,7 @@ def refund(): """ - c = get_contract(crowdfund, args=[t.a1, 50, 600]) - + c = get_contract_with_gas_estimation_for_constants(crowdfund, args=[t.a1, 50, 600]) c.participate(value=5) assert c.timelimit() == 600 assert c.deadline() - c.timestamp() == 600 @@ -90,7 +89,7 @@ def refund(): post_bal = s.head_state.get_balance(t.a1) assert post_bal - pre_bal == 54 - c = get_contract(crowdfund, args=[t.a1, 50, 600]) + c = get_contract_with_gas_estimation_for_constants(crowdfund, args=[t.a1, 50, 600]) c.participate(value=1, sender=t.k3) c.participate(value=2, sender=t.k4) c.participate(value=3, sender=t.k5) @@ -175,7 +174,7 @@ def refund(): """ - c = get_contract(crowdfund2, args=[t.a1, 50, 600]) + c = get_contract_with_gas_estimation_for_constants(crowdfund2, args=[t.a1, 50, 600]) c.participate(value=5) assert c.timelimit() == 600 @@ -191,7 +190,7 @@ def refund(): post_bal = s.head_state.get_balance(t.a1) assert post_bal - pre_bal == 54 - c = get_contract(crowdfund2, args=[t.a1, 50, 600]) + c = get_contract_with_gas_estimation_for_constants(crowdfund2, args=[t.a1, 50, 600]) c.participate(value=1, sender=t.k3) c.participate(value=2, sender=t.k4) c.participate(value=3, sender=t.k5) diff --git a/tests/parser/types/numbers/test_num256.py b/tests/parser/types/numbers/test_num256.py index 024df07f31..c576930ec0 100644 --- a/tests/parser/types/numbers/test_num256.py +++ b/tests/parser/types/numbers/test_num256.py @@ -90,7 +90,7 @@ def _num256_mulmod(x: num256, y: num256, z: num256) -> num256: return num256_mulmod(x, y, z) """ - c = get_contract(num256_code) + c = get_contract_with_gas_estimation(num256_code) t.s = s assert c._num256_mod(3, 2) == 1 diff --git a/tests/setup_transaction_tests.py b/tests/setup_transaction_tests.py index 15fe5faea0..c889356e20 100644 --- a/tests/setup_transaction_tests.py +++ b/tests/setup_transaction_tests.py @@ -12,7 +12,7 @@ #configure_logging(config_string=config_string) chain = tester.Chain() -tester.languages['viper'] = compiler.Compiler() +tester.languages['viper'] = compiler.Compiler() def inject_tx(txhex): tx = rlp.decode(ethereum_utils.decode_hex(txhex[2:]), transactions.Transaction) @@ -72,14 +72,28 @@ def get_contract_with_gas_estimation( source_code, *args, **kwargs): contract = chain.contract(source_code, language="viper", *args, **kwargs) - for func_name in contract.translator.function_data: set_decorator_to_contract_function( contract, source_code, func_name ) - return contract +def get_contract_with_gas_estimation_for_constants( + source_code, + *args, **kwargs): + abi = tester.languages['viper'].mk_full_signature(source_code) + # Take out constants from the abi for the purpose of gas estimation + for func in abi: + func['constant'] = False + ct = tester.ContractTranslator(abi) + byte_code = tester.languages['viper'].compile(source_code) + (ct.encode_constructor_arguments(kwargs['args']) if kwargs else b'') + address = chain.tx(to=b'', data=byte_code) + contract = tester.ABIContract(chain, abi, address) + for func_name in contract.translator.function_data: + set_decorator_to_contract_function( + contract, source_code, func_name + ) + return contract def get_contract(source_code, *args, **kwargs): return chain.contract(source_code, language="viper", *args, **kwargs) diff --git a/viper/parser/parser_utils.py b/viper/parser/parser_utils.py index d54add66ef..f37b8476e5 100644 --- a/viper/parser/parser_utils.py +++ b/viper/parser/parser_utils.py @@ -157,8 +157,7 @@ def __init__(self, value, args=None, typ=None, location=None, pos=None, annotati raise Exception("Invalid value for LLL AST node: %r" % self.value) assert isinstance(self.args, list) - if add_gas_estimate: - self.gas += add_gas_estimate + self.gas += add_gas_estimate def to_list(self): return [self.value] + [a.to_list() for a in self.args] From 905c63ffeaa4b4e70efe14ad57d1709015ee779a Mon Sep 17 00:00:00 2001 From: David Knott Date: Fri, 17 Nov 2017 14:43:33 -0700 Subject: [PATCH 077/162] Add gas estimation for create_with_code_of --- tests/parser/features/iteration/test_range_in.py | 4 ++-- tests/parser/functions/test_raw_call.py | 2 +- tests/parser/globals/test_getters.py | 6 +++--- tests/parser/types/numbers/test_num.py | 4 ++-- viper/functions.py | 2 +- viper/optimizer.py | 2 +- viper/parser/parser_utils.py | 5 +++-- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/parser/features/iteration/test_range_in.py b/tests/parser/features/iteration/test_range_in.py index 8f1d2d08f2..5ef2727390 100644 --- a/tests/parser/features/iteration/test_range_in.py +++ b/tests/parser/features/iteration/test_range_in.py @@ -74,7 +74,7 @@ def testin() -> bool: return True return False """ - assert_compile_failed(lambda: get_contract(code), TypeMismatchException) + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), TypeMismatchException) def test_ownership(assert_tx_failed): @@ -96,7 +96,7 @@ def is_owner() -> bool: return msg.sender in self.owners """ - c = get_contract(code) + c = get_contract_with_gas_estimation(code) assert c.is_owner() is True # contract creator is owner. assert c.is_owner(sender=t.k1) is False # no one else is. diff --git a/tests/parser/functions/test_raw_call.py b/tests/parser/functions/test_raw_call.py index 96be756d27..f1a4eb29e9 100644 --- a/tests/parser/functions/test_raw_call.py +++ b/tests/parser/functions/test_raw_call.py @@ -49,7 +49,7 @@ def create_and_return_forwarder(inp: address) -> address: return x """ - c2 = get_contract(outer_code) + c2 = get_contract_with_gas_estimation(outer_code) assert c2.create_and_call_returnten(c.address) == 10 expected_forwarder_code_mask = b'`.`\x0c`\x009`.`\x00\xf36`\x00`\x007a\x10\x00`\x006`\x00s\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Z\xf4\x15XWa\x10\x00`\x00\xf3'[12:] c3 = c2.create_and_return_forwarder(c.address) diff --git a/tests/parser/globals/test_getters.py b/tests/parser/globals/test_getters.py index 5ae5347a41..ba5fe91a6e 100644 --- a/tests/parser/globals/test_getters.py +++ b/tests/parser/globals/test_getters.py @@ -1,6 +1,6 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract + get_contract_with_gas_estimation_for_constants def test_state_accessor(): @@ -17,7 +17,7 @@ def foo() -> num: """ - c = get_contract(state_accessor) + c = get_contract_with_gas_estimation_for_constants(state_accessor) c.oo() assert c.foo() == 5 print('Passed basic state accessor test') @@ -52,7 +52,7 @@ def __init__(): self.w[3].g = 751 """ - c = get_contract(getter_code) + c = get_contract_with_gas_estimation_for_constants(getter_code) assert c.get_x() == 7 assert c.get_y(1) == 9 assert c.get_z() == b"cow" diff --git a/tests/parser/types/numbers/test_num.py b/tests/parser/types/numbers/test_num.py index 0d4bf92031..9de8ed2d57 100644 --- a/tests/parser/types/numbers/test_num.py +++ b/tests/parser/types/numbers/test_num.py @@ -28,7 +28,7 @@ def _negative_exp() -> num: return -(1+2) """ - c = get_contract(negative_nums_code) + c = get_contract_with_gas_estimation(negative_nums_code) t.s = s assert c._negative_num() == -1 assert c._negative_exp() == -3 @@ -60,7 +60,7 @@ def _num_min() -> num: return -170141183460469231731687303715884105728 # -2**127 """ - c = get_contract(num_bound_code) + c = get_contract_with_gas_estimation(num_bound_code) t.s = s NUM_MAX = 2**127 - 1 diff --git a/viper/functions.py b/viper/functions.py index f48061e31e..f308a3b79a 100644 --- a/viper/functions.py +++ b/viper/functions.py @@ -765,7 +765,7 @@ def create_with_code_of(expr, args, kwargs, context): ['mstore', placeholder, high], ['mstore', ['add', placeholder, 27], ['mul', args[0], 2**96]], ['mstore', ['add', placeholder, 47], low], - ['clamp_nonzero', ['create', value, placeholder, 64]]], typ=BaseType('address'), pos=getpos(expr)) + ['clamp_nonzero', ['create', value, placeholder, 64]]], typ=BaseType('address'), pos=getpos(expr), add_gas_estimate=10000) @signature(('num', 'decimal', 'num256'), ('num', 'decimal', 'num256')) diff --git a/viper/optimizer.py b/viper/optimizer.py index 76cbae6a7b..bd339c1805 100644 --- a/viper/optimizer.py +++ b/viper/optimizer.py @@ -104,7 +104,7 @@ def optimize(node): o.extend(arg.args) elif arg.value != "pass": o.append(arg) - return LLLnode(node.value, o, node.typ, node.location, node.pos, node.annotation) + return LLLnode(node.value, o, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate) elif hasattr(node, 'total_gas'): o = LLLnode(node.value, argz, node.typ, node.location, node.pos, node.annotation) o.total_gas = node.total_gas - node.gas + o.gas diff --git a/viper/parser/parser_utils.py b/viper/parser/parser_utils.py index f37b8476e5..1f1913dae6 100644 --- a/viper/parser/parser_utils.py +++ b/viper/parser/parser_utils.py @@ -50,6 +50,7 @@ def __init__(self, value, args=None, typ=None, location=None, pos=None, annotati self.pos = pos self.annotation = annotation self.mutable = mutable + self.add_gas_estimate = add_gas_estimate # Determine this node's valency (1 if it pushes a value on the stack, # 0 otherwise) and checks to make sure the number and valencies of # children are correct. Also, find an upper bound on gas consumption @@ -157,7 +158,7 @@ def __init__(self, value, args=None, typ=None, location=None, pos=None, annotati raise Exception("Invalid value for LLL AST node: %r" % self.value) assert isinstance(self.args, list) - self.gas += add_gas_estimate + self.gas += self.add_gas_estimate def to_list(self): return [self.value] + [a.to_list() for a in self.args] @@ -437,7 +438,7 @@ def base_type_conversion(orig, frm, to, pos=None): if not isinstance(frm, (BaseType, NullType)) or not isinstance(to, BaseType): raise TypeMismatchException("Base type conversion from or to non-base type: %r %r" % (frm, to), pos) elif is_base_type(frm, to.typ) and are_units_compatible(frm, to): - return LLLnode(orig.value, orig.args, typ=to) + return LLLnode(orig.value, orig.args, typ=to, add_gas_estimate=orig.add_gas_estimate) elif is_base_type(frm, 'num') and is_base_type(to, 'decimal') and are_units_compatible(frm, to): return LLLnode.from_list(['mul', orig, DECIMAL_DIVISOR], typ=BaseType('decimal', to.unit, to.positional)) elif is_base_type(frm, 'num256') and is_base_type(to, 'num') and are_units_compatible(frm, to): From 1574b2fa2b50ebeeeb7c5e77a2e07b9a2ab0aba1 Mon Sep 17 00:00:00 2001 From: "Jesse B. Miller" Date: Wed, 22 Nov 2017 23:29:58 -0600 Subject: [PATCH 078/162] ad logs to company.v.py with tests --- examples/stock/company.v.py | 30 ++++++++++++++++----- tests/examples/company/test_company.py | 36 +++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/examples/stock/company.v.py b/examples/stock/company.v.py index 3c51b27bee..6aea16dc28 100644 --- a/examples/stock/company.v.py +++ b/examples/stock/company.v.py @@ -3,21 +3,27 @@ total_shares: public(currency_value) price: public(num (wei / currency)) +# Financial events the contract logs +Transfer: __log__({_from: indexed(address), _to: indexed(address), _value: currency_value}) +Buy: __log__({_buyer: indexed(address), _buy_order: currency_value}) +Sell: __log__({_seller: indexed(address), _sell_order: currency_value}) +Pay: __log__({_vendor: indexed(address), _amount: wei_value}) + # Store ledger of stockholder holdings holdings: currency_value[address] # Setup company @public -def __init__(_company: address, _total_shares: currency_value, +def __init__(_company: address, _total_shares: currency_value, initial_price: num(wei / currency) ): assert _total_shares > 0 assert initial_price > 0 - + self.company = _company self.total_shares = _total_shares - + self.price = initial_price - + # Company holds all the shares at first, but can sell them all self.holdings[self.company] = _total_shares @@ -36,11 +42,14 @@ def buy_stock(): # There are enough shares to buy assert self.stock_available() >= buy_order - + # Take the shares off the market and give to stockholder self.holdings[self.company] -= buy_order self.holdings[msg.sender] += buy_order + # Log the buy event + log.Buy(msg.sender, buy_order) + # So someone can find out how much they have @public @constant @@ -68,6 +77,9 @@ def sell_stock(sell_order: currency_value): self.holdings[self.company] += sell_order send(msg.sender, sell_order * self.price) + # Log sell event + log.Sell(msg.sender, sell_order) + # Transfer stock from one stockholder to another # (Assumes the receiver is given some compensation, but not enforced) @public @@ -75,11 +87,14 @@ def transfer_stock(receiver: address, transfer_order: currency_value): assert transfer_order > 0 # AUDIT revealed this! # Can only trade as much stock as you own assert self.get_holding(msg.sender) >= transfer_order - + # Debit sender's stock and add to receiver's address self.holdings[msg.sender] -= transfer_order self.holdings[receiver] += transfer_order + # Log the transfer event + log.Transfer(msg.sender, receiver, transfer_order) + # Allows the company to pay someone for services rendered @public def pay_bill(vendor: address, amount: wei_value): @@ -91,6 +106,9 @@ def pay_bill(vendor: address, amount: wei_value): # Pay the bill! send(vendor, amount) + # Log payment event + log.Pay(vendor, amount) + # The amount a company has raised in the stock offering @public @constant diff --git a/tests/examples/company/test_company.py b/tests/examples/company/test_company.py index cfe2f8e5e9..7f5ffc6213 100644 --- a/tests/examples/company/test_company.py +++ b/tests/examples/company/test_company.py @@ -3,7 +3,7 @@ from ethereum.tools import tester as t from ethereum import utils -from tests.setup_transaction_tests import assert_tx_failed +from tests.setup_transaction_tests import assert_tx_failed, ethereum_utils as u, chain as s from viper import compiler @pytest.fixture @@ -89,3 +89,37 @@ def test_valuation(tester): test_value = int(tester.c.get_total_shares() * tester.c.get_price()) tester.c.buy_stock(sender=t.k1, value=test_value) assert tester.c.debt() == test_value + +def test_logs(tester): + # Buy is logged + tester.c.buy_stock(sender=t.k1, value=7 * tester.c.get_price()) + logs = tester.s.head_state.receipts[-1].logs + assert len(logs) == 1 + event_id = u.bytes_to_int(u.sha3(bytes('Buy(address,int128)', 'utf-8'))) + assert logs[-1].topics[0] == event_id + assert u.bytes_to_int(logs[-1].data) == 7 + + # Sell is logged + tester.c.sell_stock(3, sender=t.k1) + logs = tester.s.head_state.receipts[-1].logs + assert len(logs) == 1 + event_id = u.bytes_to_int(u.sha3(bytes('Sell(address,int128)', 'utf-8'))) + assert logs[-1].topics[0] == event_id + assert u.bytes_to_int(logs[-1].data) == 3 + + # Transfer is logged + tester.c.transfer_stock(t.a2, 4, sender=t.k1) + logs = tester.s.head_state.receipts[-1].logs + assert len(logs) == 1 + event_id = u.bytes_to_int(u.sha3(bytes('Transfer(address,address,int128)', 'utf-8'))) + assert logs[-1].topics[0] == event_id + assert u.bytes_to_int(logs[-1].data) == 4 + + # Pay is logged + amount = 10**4 + tester.c.pay_bill(t.a3, amount) + logs = tester.s.head_state.receipts[-1].logs + assert len(logs) == 1 + event_id = u.bytes_to_int(u.sha3(bytes('Pay(address,int128)', 'utf-8'))) + assert logs[-1].topics[0] == event_id + assert u.bytes_to_int(logs[-1].data) == amount From 42d224b0815b418b701303bb2c8b3d81ea224edf Mon Sep 17 00:00:00 2001 From: "Jesse B. Miller" Date: Thu, 23 Nov 2017 00:13:32 -0600 Subject: [PATCH 079/162] document event in structure of a contract --- docs/structure-of-a-contract.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index 0c3d7577e8..24a8d7ccb7 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -43,3 +43,22 @@ Functions are the executable units of code within a contract. and have different levels of visibility (:ref:`visibility-and-getters`) towards other contracts. Functions must be decorated with either @public or @internal. +.. _structure-events: + +Events +====== + +Events may be logged in specially indexed data structures that allow clients, including light clients, to efficiently search for them. + +:: + + Payment: __log__({amount: num, arg2: indexed(address)}) + + total_paid: num + + @public + def pay(): + self.total_paid += msg.value + log.Payment(msg.value, msg.sender) + +Events must be declared before global declarations and function definitions. From 55aed458e8f0057cc9f05592b04b9647fe0f557e Mon Sep 17 00:00:00 2001 From: "Jesse B. Miller" Date: Thu, 23 Nov 2017 00:27:09 -0600 Subject: [PATCH 080/162] move events before global declarations --- examples/stock/company.v.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/stock/company.v.py b/examples/stock/company.v.py index 6aea16dc28..f20d538186 100644 --- a/examples/stock/company.v.py +++ b/examples/stock/company.v.py @@ -1,14 +1,14 @@ -# Own shares of a company! -company: public(address) -total_shares: public(currency_value) -price: public(num (wei / currency)) - # Financial events the contract logs Transfer: __log__({_from: indexed(address), _to: indexed(address), _value: currency_value}) Buy: __log__({_buyer: indexed(address), _buy_order: currency_value}) Sell: __log__({_seller: indexed(address), _sell_order: currency_value}) Pay: __log__({_vendor: indexed(address), _amount: wei_value}) +# Own shares of a company! +company: public(address) +total_shares: public(currency_value) +price: public(num (wei / currency)) + # Store ledger of stockholder holdings holdings: currency_value[address] From ba497c80e823729fe1c368679a85297c7829acb2 Mon Sep 17 00:00:00 2001 From: "Jesse B. Miller" Date: Thu, 23 Nov 2017 00:30:35 -0600 Subject: [PATCH 081/162] remove unused import --- tests/examples/company/test_company.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/examples/company/test_company.py b/tests/examples/company/test_company.py index 7f5ffc6213..0dea4ebcd1 100644 --- a/tests/examples/company/test_company.py +++ b/tests/examples/company/test_company.py @@ -3,7 +3,7 @@ from ethereum.tools import tester as t from ethereum import utils -from tests.setup_transaction_tests import assert_tx_failed, ethereum_utils as u, chain as s +from tests.setup_transaction_tests import assert_tx_failed, ethereum_utils as u from viper import compiler @pytest.fixture From 36527676ad66da01ce785a8891a6cc60217edf43 Mon Sep 17 00:00:00 2001 From: Adhika Setya Pramudita Date: Thu, 23 Nov 2017 19:12:43 +0700 Subject: [PATCH 082/162] types.py: Remove duplicated method Fix https://github.com/ethereum/viper/issues/488 --- viper/types.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/viper/types.py b/viper/types.py index 7f10012070..b384f930b9 100644 --- a/viper/types.py +++ b/viper/types.py @@ -4,6 +4,7 @@ from .exceptions import InvalidTypeException from .utils import ( base_types, + ceil32, is_varname_valid, valid_units, ) @@ -311,11 +312,6 @@ def parse_type(item, location): raise InvalidTypeException("Invalid type: %r" % ast.dump(item), item) -# Rounds up to nearest 32, eg. 95 -> 96, 96 -> 96, 97 -> 128 -def ceil32(x): - return x + 31 - (x - 1) % 32 - - # Gets the number of memory or storage keys needed to represent a given type def get_size_of_type(typ): if isinstance(typ, BaseType): From 4c2f0bf043e7531452d81191ed207ac48aae99d7 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Thu, 23 Nov 2017 16:36:05 +0200 Subject: [PATCH 083/162] Adds support -f bytecode_runtime. --- bin/viper | 4 +++- viper/compiler.py | 2 +- viper/parser/parser.py | 13 ++++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/bin/viper b/bin/viper index bffde9b087..36b0f444a3 100755 --- a/bin/viper +++ b/bin/viper @@ -11,7 +11,7 @@ from viper.parser import parser_utils parser = argparse.ArgumentParser(description='Viper {0} programming language for Ethereum'.format(viper.__version__)) parser.add_argument('input_file', help='Viper sourcecode to compile') -parser.add_argument('-f', help='Format to print', choices=['abi', 'json', 'bytecode', 'ir'], default='bytecode', dest='format') +parser.add_argument('-f', help='Format to print', choices=['abi', 'json', 'bytecode', 'bytecode_runtime', 'ir'], default='bytecode', dest='format') parser.add_argument('--show-gas-estimates', help='Show gas estimates in ir output mode.', action="store_true") args = parser.parse_args() @@ -29,5 +29,7 @@ if __name__ == '__main__': print(json.dumps(compiler.mk_full_signature(code))) elif args.format == 'bytecode': print('0x' + compiler.compile(code).hex()) + elif args.format == 'bytecode_runtime': + print('0x' + compiler.compile(code, bytecode_runtime=True).hex()) elif args.format == 'ir': print(optimizer.optimize(parse_to_lll(code))) diff --git a/viper/compiler.py b/viper/compiler.py index ca98150024..c884e653bf 100644 --- a/viper/compiler.py +++ b/viper/compiler.py @@ -8,7 +8,7 @@ def memsize_to_gas(memsize): def compile(code, *args, **kwargs): - lll = optimizer.optimize(parser.parse_tree_to_lll(parser.parse(code), code)) + lll = optimizer.optimize(parser.parse_tree_to_lll(parser.parse(code), code, runtime_only=kwargs.get('bytecode_runtime', False))) return compile_lll.assembly_to_evm(compile_lll.compile_to_assembly(lll)) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 8eee544746..3502062666 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -327,7 +327,7 @@ def parse_external_contracts(external_contracts, _contracts): return external_contracts -def parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, origcode): +def parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, origcode, runtime_only=False): sub = ['seq', initializer_lll] add_gas = initializer_lll.gas for _def in otherfuncs: @@ -337,12 +337,15 @@ def parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, ori sig = FunctionSignature.from_definition(_def) sig.gas = sub[-1].total_gas sigs[sig.name] = sig - o.append(['return', 0, ['lll', sub, 0]]) - return o + if runtime_only: + return sub + else: + o.append(['return', 0, ['lll', sub, 0]]) + return o # Main python parse tree => LLL method -def parse_tree_to_lll(code, origcode): +def parse_tree_to_lll(code, origcode, runtime_only=False): _contracts, _events, _defs, _globals = get_contracts_and_defs_and_globals(code) _names = [_def.name for _def in _defs] + [_event.target.id for _event in _events] # Checks for duplicate funciton / event names @@ -366,7 +369,7 @@ def parse_tree_to_lll(code, origcode): o.append(parse_func(initfunc[0], _globals, {**{'self': sigs}, **external_contracts}, origcode)) # If there are regular functions... if otherfuncs: - o = parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, origcode) + o = parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, origcode, runtime_only) return LLLnode.from_list(o, typ=None) From 098f42534b62f75ee92528352dc9236ee5107dc1 Mon Sep 17 00:00:00 2001 From: "Jesse B. Miller" Date: Thu, 23 Nov 2017 09:32:37 -0600 Subject: [PATCH 084/162] cleans up test_logs --- tests/examples/company/test_company.py | 35 +++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/tests/examples/company/test_company.py b/tests/examples/company/test_company.py index 0dea4ebcd1..0b2dbd91a0 100644 --- a/tests/examples/company/test_company.py +++ b/tests/examples/company/test_company.py @@ -3,7 +3,7 @@ from ethereum.tools import tester as t from ethereum import utils -from tests.setup_transaction_tests import assert_tx_failed, ethereum_utils as u +from tests.setup_transaction_tests import assert_tx_failed, ethereum_utils as u, get_logs from viper import compiler @pytest.fixture @@ -93,33 +93,34 @@ def test_valuation(tester): def test_logs(tester): # Buy is logged tester.c.buy_stock(sender=t.k1, value=7 * tester.c.get_price()) - logs = tester.s.head_state.receipts[-1].logs + receipt = tester.s.head_state.receipts[-1] + logs = get_logs(receipt, tester.c) assert len(logs) == 1 - event_id = u.bytes_to_int(u.sha3(bytes('Buy(address,int128)', 'utf-8'))) - assert logs[-1].topics[0] == event_id - assert u.bytes_to_int(logs[-1].data) == 7 + assert logs[0]["_event_type"] == b'Buy' + assert logs[0]["_buy_order"] == 7 # Sell is logged tester.c.sell_stock(3, sender=t.k1) - logs = tester.s.head_state.receipts[-1].logs + receipt = tester.s.head_state.receipts[-1] + logs = get_logs(receipt, tester.c) assert len(logs) == 1 - event_id = u.bytes_to_int(u.sha3(bytes('Sell(address,int128)', 'utf-8'))) - assert logs[-1].topics[0] == event_id - assert u.bytes_to_int(logs[-1].data) == 3 + assert logs[0]["_event_type"] == b'Sell' + assert logs[0]["_sell_order"] == 3 # Transfer is logged tester.c.transfer_stock(t.a2, 4, sender=t.k1) - logs = tester.s.head_state.receipts[-1].logs + receipt = tester.s.head_state.receipts[-1] + logs = get_logs(receipt, tester.c) assert len(logs) == 1 - event_id = u.bytes_to_int(u.sha3(bytes('Transfer(address,address,int128)', 'utf-8'))) - assert logs[-1].topics[0] == event_id - assert u.bytes_to_int(logs[-1].data) == 4 + assert logs[0]["_event_type"] == b'Transfer' + assert logs[0]["_value"] == 4 # Pay is logged amount = 10**4 tester.c.pay_bill(t.a3, amount) - logs = tester.s.head_state.receipts[-1].logs + receipt = tester.s.head_state.receipts[-1] + logs = get_logs(receipt, tester.c) assert len(logs) == 1 - event_id = u.bytes_to_int(u.sha3(bytes('Pay(address,int128)', 'utf-8'))) - assert logs[-1].topics[0] == event_id - assert u.bytes_to_int(logs[-1].data) == amount + assert logs[0]["_event_type"] == b'Pay' + assert logs[0]["_amount"] == amount + From 4000244e0ca0de26ff8868cc7306518ee246dca7 Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 21 Nov 2017 18:25:02 -0700 Subject: [PATCH 085/162] Use revert opcode for assert statements --- tests/parser/features/test_assert.py | 18 ++++++++++++++++++ viper/compile_lll.py | 22 +++++++++++++--------- viper/opcodes.py | 5 +++-- 3 files changed, 34 insertions(+), 11 deletions(-) create mode 100644 tests/parser/features/test_assert.py diff --git a/tests/parser/features/test_assert.py b/tests/parser/features/test_assert.py new file mode 100644 index 0000000000..1f6b762f77 --- /dev/null +++ b/tests/parser/features/test_assert.py @@ -0,0 +1,18 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation, get_contract + +def test_assert_refund(t): + code = """ +@public +def foo(): + assert 1 == 2 +""" + c = get_contract_with_gas_estimation(code) + pre_balance = t.s.head_state.get_balance(t.a0) + with pytest.raises(t.TransactionFailed): + c.foo(startgas=10**6, gasprice=10) + post_balance = t.s.head_state.get_balance(t.a0) + # Checks for gas refund from revert + # 10**5 is added to account for gas used before the transactions fails + assert pre_balance < post_balance + 10**5 diff --git a/viper/compile_lll.py b/viper/compile_lll.py index f6654b6b57..b4044a3875 100644 --- a/viper/compile_lll.py +++ b/viper/compile_lll.py @@ -155,7 +155,10 @@ def compile_to_assembly(code, withargs=None, break_dest=None, height=0): # Assert (if false, exit) elif code.value == 'assert': o = compile_to_assembly(code.args[0], withargs, break_dest, height) - o.extend(['ISZERO', 'PC', 'JUMPI']) + end_symbol = mksymbol() + o.extend([end_symbol, 'JUMPI']) + o.extend(['PUSH1', 0, 'DUP1', 'REVERT']) + o.extend([end_symbol, 'JUMPDEST']) return o # Unsigned/signed clamp, check less-than elif code.value in ('uclamplt', 'uclample', 'clamplt', 'clample', 'uclampgt', 'uclampge', 'clampgt', 'clampge'): @@ -173,21 +176,22 @@ def compile_to_assembly(code, withargs=None, break_dest=None, height=0): o.extend(['DUP2']) # Stack: num num bound if code.value == 'uclamplt': - o.extend(["LT", 'ISZERO', 'PC', 'JUMPI']) + o.extend(["LT", 'ISZERO']) elif code.value == "clamplt": - o.extend(["SLT", 'ISZERO', 'PC', 'JUMPI']) + o.extend(["SLT", 'ISZERO']) elif code.value == "uclample": - o.extend(["GT", 'PC', 'JUMPI']) + o.extend(["GT"]) elif code.value == "clample": - o.extend(["SGT", 'PC', 'JUMPI']) + o.extend(["SGT"]) elif code.value == 'uclampgt': - o.extend(["GT", 'ISZERO', 'PC', 'JUMPI']) + o.extend(["GT", 'ISZERO']) elif code.value == "clampgt": - o.extend(["SGT", 'ISZERO', 'PC', 'JUMPI']) + o.extend(["SGT", 'ISZERO']) elif code.value == "uclampge": - o.extend(["LT", 'PC', 'JUMPI']) + o.extend(["LT"]) elif code.value == "clampge": - o.extend(["SLT", 'PC', 'JUMPI']) + o.extend(["SLT"]) + o.extend(['PC', 'JUMPI']) return o # Signed clamp, check against upper and lower bounds elif code.value in ('clamp', 'uclamp'): diff --git a/viper/opcodes.py b/viper/opcodes.py index 1b972b478d..4c059b4edb 100644 --- a/viper/opcodes.py +++ b/viper/opcodes.py @@ -65,10 +65,11 @@ 'RETURN': [0xf3, 2, 0, 0], 'DELEGATECALL': [0xf4, 6, 1, 700], 'CALLBLACKBOX': [0xf5, 7, 1, 700], - 'INVALID': [0xfe, 0, 0, 0], - 'SUICIDE': [0xff, 1, 0, 5000], 'SELFDESTRUCT': [0xff, 1, 0, 25000], 'STATICCALL': [0xfa, 6, 1, 40], + 'REVERT': [0xfd, 2, 0, 0], + 'SUICIDE': [0xff, 1, 0, 5000], + 'INVALID': [0xfe, 0, 0, 0], } pseudo_opcodes = { From f24499a317fe12712855cfc3ea812a82713aa3bc Mon Sep 17 00:00:00 2001 From: Adhika Setya Pramudita Date: Fri, 24 Nov 2017 00:49:19 +0700 Subject: [PATCH 086/162] Show lineno of base_type_conversion error Fix https://github.com/ethereum/viper/issues/498 --- viper/parser/parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 8eee544746..8a5660fa1a 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -653,7 +653,7 @@ def pack_logging_topics(event_id, args, topics_types, context): topics.append(byte_array_to_num(input, arg, 'num256', size)) else: input = unwrap_location(input) - input = base_type_conversion(input, input.typ, typ) + input = base_type_conversion(input, input.typ, typ, arg) topics.append(input) return topics @@ -661,7 +661,7 @@ def pack_logging_topics(event_id, args, topics_types, context): def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): if isinstance(typ, BaseType): input = parse_expr(arg, context) - input = base_type_conversion(input, input.typ, typ) + input = base_type_conversion(input, input.typ, typ, arg) holder.append(LLLnode.from_list(['mstore', placeholder, input], typ=typ, location='memory')) elif isinstance(typ, ByteArrayType): bytez = b'' From 0b691a5014631c0af73245a5ad9a6fe0943c2391 Mon Sep 17 00:00:00 2001 From: Adhika Setya Pramudita Date: Fri, 24 Nov 2017 00:51:36 +0700 Subject: [PATCH 087/162] Show lineno of undeclared function visibility Fix https://github.com/ethereum/viper/issues/489 --- viper/function_signature.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper/function_signature.py b/viper/function_signature.py index e31c16abd3..225af7dd88 100644 --- a/viper/function_signature.py +++ b/viper/function_signature.py @@ -85,7 +85,7 @@ def from_definition(cls, code): if public and internal: raise StructureException("Cannot use public and internal decorators on the same function") if not public and not internal and not isinstance(code.body[0], ast.Pass): - raise StructureException("Function visibility must be declared (@public or @internal)") + raise StructureException("Function visibility must be declared (@public or @internal)", code) # Determine the return type and whether or not it's constant. Expects something # of the form: # def foo(): ... From c07bc16e4f8fe323405c3403fcf097eeed46c2e7 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Fri, 24 Nov 2017 14:14:01 +0200 Subject: [PATCH 088/162] Adds basic test for bytecode_runtime generation. --- tests/compiler/test_bytecode_runtime.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/compiler/test_bytecode_runtime.py diff --git a/tests/compiler/test_bytecode_runtime.py b/tests/compiler/test_bytecode_runtime.py new file mode 100644 index 0000000000..454d05f392 --- /dev/null +++ b/tests/compiler/test_bytecode_runtime.py @@ -0,0 +1,15 @@ +from viper import compiler + + +def test_bytecode_runtime(): + code = """ +@public +def a() -> bool: + return true + """ + + bytecode = compiler.compile(code) + bytecode_runtime = compiler.compile(code, bytecode_runtime=True) + + assert len(bytecode) > len(bytecode_runtime) + assert bytecode_runtime in bytecode From ae01c4b787f5864140fec20e9732586d9bfa6398 Mon Sep 17 00:00:00 2001 From: David Knott Date: Fri, 24 Nov 2017 10:42:55 -0700 Subject: [PATCH 089/162] add clamp_nonzero to modulo arithemetic --- viper/parser/expr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper/parser/expr.py b/viper/parser/expr.py index 9cd004faf9..727cf40e26 100644 --- a/viper/parser/expr.py +++ b/viper/parser/expr.py @@ -280,7 +280,7 @@ def arithmetic(self): o = LLLnode.from_list(['smod', left, ['mul', ['clamp_nonzero', right], DECIMAL_DIVISOR]], typ=BaseType('decimal', new_unit), pos=getpos(self.expr)) elif ltyp == 'num' and rtyp == 'decimal': - o = LLLnode.from_list(['smod', ['mul', left, DECIMAL_DIVISOR], right], + o = LLLnode.from_list(['smod', ['mul', left, DECIMAL_DIVISOR], ['clamp_nonzero', right]], typ=BaseType('decimal', new_unit), pos=getpos(self.expr)) elif isinstance(self.expr.op, ast.Pow): if left.typ.positional or right.typ.positional: From 1cfccc07640d1c8a51fb5ef625fc07e3a343292e Mon Sep 17 00:00:00 2001 From: David Knott Date: Fri, 24 Nov 2017 10:43:09 -0700 Subject: [PATCH 090/162] Add modulo tests and refactor simple auction example --- tests/conftest.py | 6 +- .../auctions/test_simple_open_auction.py | 31 +++++----- .../parser/features/arithmetic/test_modulo.py | 58 +++++++++++++++++++ 3 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 tests/parser/features/arithmetic/test_modulo.py diff --git a/tests/conftest.py b/tests/conftest.py index 357a5e040d..392e9b6989 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,12 +28,12 @@ def lll_compiler(lll): return lll_compiler @pytest.fixture -def assert_tx_failed(): +def assert_tx_failed(t): def assert_tx_failed(function_to_test, exception = tester.TransactionFailed): - initial_state = tester.s.snapshot() + initial_state = t.s.snapshot() with pytest.raises(exception): function_to_test() - tester.s.revert(initial_state) + t.s.revert(initial_state) return assert_tx_failed @pytest.fixture diff --git a/tests/examples/auctions/test_simple_open_auction.py b/tests/examples/auctions/test_simple_open_auction.py index d5922a71e3..ae699ad4dc 100644 --- a/tests/examples/auctions/test_simple_open_auction.py +++ b/tests/examples/auctions/test_simple_open_auction.py @@ -1,23 +1,20 @@ import pytest -from ethereum.tools import tester import ethereum.utils as utils FIVE_DAYS = 432000 @pytest.fixture -def auction_tester(): - t = tester - tester.s = t.Chain() +def auction_tester(t): from viper import compiler t.languages['viper'] = compiler.Compiler() contract_code = open('examples/auctions/simple_open_auction.v.py').read() - tester.c = tester.s.contract(contract_code, language='viper', args=[tester.accounts[0], FIVE_DAYS]) - return tester + t.c = t.s.contract(contract_code, language='viper', args=[t.accounts[0], FIVE_DAYS]) + return t def test_initial_state(auction_tester): # Check beneficiary is correct - assert utils.remove_0x_head(auction_tester.c.get_beneficiary()) == tester.accounts[0].hex() + assert utils.remove_0x_head(auction_tester.c.get_beneficiary()) == auction_tester.accounts[0].hex() # Check bidding time is 5 days assert auction_tester.c.get_auction_end() == auction_tester.s.head_state.timestamp + 432000 # Check start time is current block timestamp @@ -31,43 +28,45 @@ def test_initial_state(auction_tester): def test_bid(auction_tester, assert_tx_failed): + auction_tester.s.mine() # Bidder cannot bid 0 assert_tx_failed(lambda: auction_tester.c.bid(value=0, sender=auction_tester.k1)) # Bidder can bid - auction_tester.c.bid(value=1, sender=tester.k1) + auction_tester.c.bid(value=1, sender=auction_tester.k1) # Check that higest bidder and highest bid have changed accordingly assert utils.remove_0x_head(auction_tester.c.get_highest_bidder()) == auction_tester.accounts[1].hex() assert auction_tester.c.get_highest_bid() == 1 # Bidder bid cannot equal current highest bid assert_tx_failed(lambda: auction_tester.c.bid(value=1, sender=auction_tester.k1)) # Higher bid can replace current highest bid - auction_tester.c.bid(value=2, sender=tester.k2) + auction_tester.c.bid(value=2, sender=auction_tester.k2) # Check that higest bidder and highest bid have changed accordingly assert utils.remove_0x_head(auction_tester.c.get_highest_bidder()) == auction_tester.accounts[2].hex() assert auction_tester.c.get_highest_bid() == 2 # Multiple bidders can bid - auction_tester.c.bid(value=3, sender=tester.k3) - auction_tester.c.bid(value=4, sender=tester.k4) - auction_tester.c.bid(value=5, sender=tester.k5) + auction_tester.c.bid(value=3, sender=auction_tester.k3) + auction_tester.c.bid(value=4, sender=auction_tester.k4) + auction_tester.c.bid(value=5, sender=auction_tester.k5) # Check that higest bidder and highest bid have changed accordingly assert utils.remove_0x_head(auction_tester.c.get_highest_bidder()) == auction_tester.accounts[5].hex() assert auction_tester.c.get_highest_bid() == 5 - auction_tester.c.bid(value=1 * 10**10, sender=tester.k1) + auction_tester.c.bid(value=1 * 10**10, sender=auction_tester.k1) balance_before_out_bid = auction_tester.s.head_state.get_balance(auction_tester.accounts[1]) - auction_tester.c.bid(value=2 * 10**10, sender=tester.k2) + auction_tester.c.bid(value=2 * 10**10, sender=auction_tester.k2) balance_after_out_bid = auction_tester.s.head_state.get_balance(auction_tester.accounts[1]) # Account has more money after its bid is out bid assert balance_after_out_bid > balance_before_out_bid def test_auction_end(auction_tester, assert_tx_failed): + auction_tester.s.mine() # Fails if auction end time has not been reached assert_tx_failed(lambda: auction_tester.c.auction_end()) - auction_tester.c.bid(value=1 * 10**10, sender=tester.k2) + auction_tester.c.bid(value=1 * 10**10, sender=auction_tester.k2) # Move block timestamp foreward to reach auction end time auction_tester.s.head_state.timestamp += FIVE_DAYS balance_before_end = auction_tester.s.head_state.get_balance(auction_tester.accounts[0]) - auction_tester.c.auction_end(sender=tester.k2) + auction_tester.c.auction_end(sender=auction_tester.k2) balance_after_end = auction_tester.s.head_state.get_balance(auction_tester.accounts[0]) # Beneficiary receives the highest bid assert balance_after_end == balance_before_end + 1 * 10 ** 10 diff --git a/tests/parser/features/arithmetic/test_modulo.py b/tests/parser/features/arithmetic/test_modulo.py new file mode 100644 index 0000000000..eae1418f32 --- /dev/null +++ b/tests/parser/features/arithmetic/test_modulo.py @@ -0,0 +1,58 @@ +import pytest +from viper.exceptions import TypeMismatchException +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation, get_contract + + +def test_modulo(): + code = """ +@public +def num_modulo_num() -> num: + return 1 % 2 + +@public +def decimal_modulo_decimal() -> decimal: + return 1.5 % .33 + +@public +def decimal_modulo_num() -> decimal: + return .5 % 1 + + +@public +def num_modulo_decimal() -> decimal: + return 1.5 % 1 +""" + c = get_contract_with_gas_estimation(code) + assert c.num_modulo_num() == 1 + assert c.decimal_modulo_decimal() == .18 + assert c.decimal_modulo_num() == .5 + assert c.num_modulo_decimal() == .5 + + +def test_modulo_with_different_units(assert_compile_failed): + code = """ +@public +def foo(a: currency_value, b: num): + x = a % b +""" + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), TypeMismatchException) + + +def test_modulo_with_positional_input(assert_compile_failed): + code = """ +@public +def foo(a: num(sec, positional), b: num): + x = a % b +""" + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), TypeMismatchException) + + +def test_modulo_with_input_of_zero(assert_tx_failed): + code = """ +@public +def foo(a: num, b: decimal) -> decimal: + return a % b +""" + c = get_contract_with_gas_estimation(code) + assert_tx_failed(lambda: c.foo(1, 0)) From 3ebe69499f5a4c2e27659b730b40327ab100eb14 Mon Sep 17 00:00:00 2001 From: David Knott Date: Wed, 22 Nov 2017 15:26:31 -0700 Subject: [PATCH 091/162] Use `with` to decrease repetition --- viper/parser/parser_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/viper/parser/parser_utils.py b/viper/parser/parser_utils.py index 1f1913dae6..b9e819d7a9 100644 --- a/viper/parser/parser_utils.py +++ b/viper/parser/parser_utils.py @@ -255,8 +255,9 @@ def make_byte_array_copier(destination, source): gas_calculation = opcodes.GIDENTITYBASE + \ opcodes.GIDENTITYWORD * (ceil32(source.typ.maxlen) // 32) o = LLLnode.from_list( - ['with', '_sz', ['add', 32, ['mload', source]], - ['assert', ['call', ['add', 18, ['div', '_sz', 10]], 4, 0, source, '_sz', destination, '_sz']]], typ=None, add_gas_estimate=gas_calculation) + ['with', '_source', source, + ['with', '_sz', ['add', 32, ['mload', '_source']], + ['assert', ['call', ['add', 18, ['div', '_sz', 10]], 4, 0, '_source', '_sz', destination, '_sz']]]], typ=None, add_gas_estimate=gas_calculation) return o pos_node = LLLnode.from_list('_pos', typ=source.typ, location=source.location) From 8e96d121c66bde142cfaa5770129a10c3809c89b Mon Sep 17 00:00:00 2001 From: David Knott Date: Sat, 25 Nov 2017 09:50:24 -0700 Subject: [PATCH 092/162] Change vipercoin from returning false to throwing --- examples/tokens/vipercoin.v.py | 32 +++++++++++-------------- tests/examples/tokens/test_vipercoin.py | 8 +++---- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/examples/tokens/vipercoin.v.py b/examples/tokens/vipercoin.v.py index 864f11d6da..2c9d8ba1ca 100644 --- a/examples/tokens/vipercoin.v.py +++ b/examples/tokens/vipercoin.v.py @@ -58,33 +58,29 @@ def totalSupply() -> num256: @public def transfer(_to: address, _amount: num(num256)) -> bool: - if self.balances[msg.sender] >= _amount and \ - self.balances[_to] + _amount >= self.balances[_to]: + assert self.balances[msg.sender] >= _amount + assert self.balances[_to] + _amount >= self.balances[_to] - self.balances[msg.sender] -= _amount # Subtract from the sender - self.balances[_to] += _amount # Add the same to the recipient - log.Transfer(msg.sender, _to, as_num256(_amount)) # log transfer event. + self.balances[msg.sender] -= _amount # Subtract from the sender + self.balances[_to] += _amount # Add the same to the recipient + log.Transfer(msg.sender, _to, as_num256(_amount)) # log transfer event. - return True - else: - return False + return True # Transfer allowed tokens from a specific account to another. @public def transferFrom(_from: address, _to: address, _value: num(num256)) -> bool: - if _value <= self.allowed[_from][msg.sender] and \ - _value <= self.balances[_from]: - - self.balances[_from] -= _value # decrease balance of from address. - self.allowed[_from][msg.sender] -= _value # decrease allowance. - self.balances[_to] += _value # incease balance of to address. - log.Transfer(_from, _to, as_num256(_value)) # log transfer event. + assert _value <= self.allowed[_from][msg.sender] + assert _value <= self.balances[_from] - return True - else: - return False + self.balances[_from] -= _value # decrease balance of from address. + self.allowed[_from][msg.sender] -= _value # decrease allowance. + self.balances[_to] += _value # incease balance of to address. + log.Transfer(_from, _to, as_num256(_value)) # log transfer event. + + return True # Allow _spender to withdraw from your account, multiple times, up to the _value amount. diff --git a/tests/examples/tokens/test_vipercoin.py b/tests/examples/tokens/test_vipercoin.py index a29b5c46dd..e6cda17011 100644 --- a/tests/examples/tokens/test_vipercoin.py +++ b/tests/examples/tokens/test_vipercoin.py @@ -49,10 +49,8 @@ def test_transfer(token_tester, assert_tx_failed): assert token_tester.c.balanceOf(token_tester.accounts[1]) == 1 assert token_tester.c.balanceOf(token_tester.accounts[0]) == TOKEN_TOTAL_SUPPLY - 1 - # Some edge cases: - # more than allowed - assert token_tester.c.transfer(token_tester.accounts[1], TOKEN_TOTAL_SUPPLY) is False + assert_tx_failed(lambda: token_tester.c.transfer(token_tester.accounts[1], TOKEN_TOTAL_SUPPLY)) # Negative transfer value. assert_tx_failed( @@ -87,7 +85,7 @@ def test_transferFrom(token_tester, assert_tx_failed): assert contract.allowance(a0, a1) == ALLOWANCE - 3 # a2 may not transfer. - assert contract.transferFrom(a0, a2, ALLOWANCE, sender=k2) is False + assert_tx_failed(lambda: contract.transferFrom(a0, a2, ALLOWANCE, sender=k2)) # Negative transfer value. assert_tx_failed( @@ -96,7 +94,7 @@ def test_transferFrom(token_tester, assert_tx_failed): ) # Transfer more than allowance: - assert contract.transferFrom(a0, a2, 8, sender=k1) is False + assert_tx_failed(lambda: contract.transferFrom(a0, a2, 8, sender=k1)) assert contract.balanceOf(a0) == TOKEN_TOTAL_SUPPLY - 3 assert contract.balanceOf(a1) == 0 assert contract.balanceOf(a2) == 3 From 827c7c5c44934511d630703ed3b8a0af11299301 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sat, 25 Nov 2017 09:34:30 -0700 Subject: [PATCH 093/162] Add check to as_num256 to make sure number is greater than 0 --- tests/parser/functions/test_as_num256.py | 21 +++++++++++++++++++++ viper/functions.py | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 tests/parser/functions/test_as_num256.py diff --git a/tests/parser/functions/test_as_num256.py b/tests/parser/functions/test_as_num256.py new file mode 100644 index 0000000000..d784c5f33d --- /dev/null +++ b/tests/parser/functions/test_as_num256.py @@ -0,0 +1,21 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation, get_contract + +def test_as_num256_with_negative_num(assert_compile_failed): + code = """ +@public +def foo() -> num256: + return as_num256(1-2) +""" + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), Exception) + + +def test_as_num256_with_negative_input(assert_tx_failed): + code = """ +@public +def foo(x: num) -> num256: + return as_num256(x) +""" + c = get_contract_with_gas_estimation(code) + assert_tx_failed(lambda: c.foo(-1)) diff --git a/viper/functions.py b/viper/functions.py index f308a3b79a..df57329140 100644 --- a/viper/functions.py +++ b/viper/functions.py @@ -183,9 +183,9 @@ def as_num256(expr, args, kwargs, context): if isinstance(args[0], int): if not(0 <= args[0] <= 2**256 - 1): raise InvalidLiteralException("Number out of range: " + str(expr.args[0].n), expr.args[0]) - return LLLnode.from_list(args[0], typ=BaseType('num256', None), pos=getpos(expr)) + return LLLnode.from_list(args[0], typ=BaseType('num256'), pos=getpos(expr)) elif isinstance(args[0], LLLnode): - return LLLnode(value=args[0].value, args=args[0].args, typ=BaseType('num256'), pos=getpos(expr)) + return LLLnode.from_list(['clampge', args[0], 0], typ=BaseType('num256'), pos=getpos(expr)) else: raise InvalidLiteralException("Invalid input for num256: %r" % args[0], expr) From 82bc514fb9484943372aa2f5868bed85c3c6953a Mon Sep 17 00:00:00 2001 From: Daniel Tsui Date: Sun, 26 Nov 2017 17:07:48 +0800 Subject: [PATCH 094/162] Improve contributing .rst - Fixes a link that wasn't displaying correctly. - Makes some instructions more concise. - Changes old `:ref:building-from-source`, to link to installation instructions. --- docs/contributing.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index d147349090..cdd891859b 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -4,7 +4,7 @@ Contributing Help is always appreciated! -To get started, you can try :ref:`building-from-source` in order to familiarize +To get started, you can try `installing Viper `_ in order to familiarize yourself with the components of Viper and the build process. Also, it may be useful to become well-versed at writing smart-contracts in Viper. @@ -18,9 +18,8 @@ In particular, we need help in the following areas: `_ and the `Viper Gitter `_ * Suggesting Improvements -`_ -* Fixing and responding to `Viper's GitHub issues - `_ +* Fixing and responding to `Viper's GitHub issues `_ + How to Suggest Improvements @@ -50,7 +49,7 @@ very helpful and sometimes even clarifies a misunderstanding. Fix Bugs ======== -Go through the `GitHub issues `_ or report bugs at https://github.com/ethereum/viper/issues for bugs. Anything tagged with "bug" is open to whoever wants to implement it. +Find or report bugs at our `issues page `_. Anything tagged with "bug" is open to whoever wants to implement it. Workflow for Pull Requests ========================== @@ -65,11 +64,11 @@ and instead, ``git rebase`` your branch. **Implement Features** -Additionally, if you are writing a new feature, please ensure you write appropriate +If you are writing a new feature, please ensure you write appropriate Boost test cases and place them under ``tests/``. -However, if you are making a larger change, please consult with the Gitter channel, first. +If you are making a larger change, please consult first with the Gitter channel. -Also, even though we do CI testing, please make sure that the tests pass for supported Python version and ensure that it builds locally before submitting a pull request. +Although we do CI testing, please make sure that the tests pass for supported Python version and ensure that it builds locally before submitting a pull request. -Thank you for your help! +Thank you for your help! ​ From 7f6e52f3c93338a936aac8200464c711764bd0c3 Mon Sep 17 00:00:00 2001 From: Bryant Eisenbach Date: Sun, 26 Nov 2017 10:55:42 -0500 Subject: [PATCH 095/162] Added testing and deployment doc --- docs/testing-deploying-contracts.rst | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 docs/testing-deploying-contracts.rst diff --git a/docs/testing-deploying-contracts.rst b/docs/testing-deploying-contracts.rst new file mode 100644 index 0000000000..4d3a204cc9 --- /dev/null +++ b/docs/testing-deploying-contracts.rst @@ -0,0 +1,49 @@ +.. index:: testing;deploying, testing + +################## +Testing a Contract +################## + +The following example demonstrates how to compile and deploy your viper contract. +It requires ``pyethereum>=2.0.0`` for the ``tester`` module + +.. code-block:: python + from viper import compiler + from ethereum.tools import tester + + # Get a new chain + chain = tester.Chain() + # Set the viper compiler to run when the viper language is requested + tester.languages['viper'] = compiler.Compiler() + + with open('my_contract.vy' 'r') as f: + source_code = f.read() + # Compile and Deploy contract to provisioned testchain + # (e.g. run __init__ method) with given args (e.g. init_args) + # from msg.sender = t.k1 (private key of address 1 in test acconuts) + # and supply 1000 wei to the contract + init_args = ['arg1', 'arg2', 3] + contract = chain.contract(source_code, language="viper", + init_args, sender=t.k1, value=1000) + + contract.myMethod() # Executes myMethod on the tester "chain" + chain.mine() # Mines the above transaction (and any before it) into a block + +Note: We are working on integration with `ethereum-tester `_, +so this example will change. + +#################### +Deploying a Contract +#################### + +You have several options to deploy a Viper contract to the public testnets. + +One option is to take the bytecode generated by the viper compiler and manually deploy it through mist or geth: + +.. code-block:: bash + viper yourFileName.vy + # returns bytecode + +Deploying from bytecode is currently outside the scope of this example. +We are working on integration with `populus `_, +this will be the preferred way of deploying viper contracts in the future. From 2dcf9e0dc8e4b4e5809829ee5a7624cad6c1d430 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sun, 26 Nov 2017 07:30:41 -0700 Subject: [PATCH 096/162] Fix augassignment checks --- tests/parser/features/test_assignment.py | 21 ++++++- viper/parser/stmt.py | 71 +++++++++++++----------- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/tests/parser/features/test_assignment.py b/tests/parser/features/test_assignment.py index 45188549bf..12b078417b 100644 --- a/tests/parser/features/test_assignment.py +++ b/tests/parser/features/test_assignment.py @@ -1,9 +1,10 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ get_contract_with_gas_estimation, get_contract +from viper.exceptions import ConstancyViolationException -def test_augassign_test(): +def test_augassign(): augassign_test = """ @public def augadd(x: num, y: num) -> num: @@ -44,3 +45,21 @@ def augmod(x: num, y: num) -> num: assert c.augdiv(5, 12) == 0 assert c.augmod(5, 12) == 5 print('Passed aug-assignment test') + + +def test_invalid_assign(assert_compile_failed): + code = """ +@public +def foo(x:num): + x = 5 +""" + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), ConstancyViolationException) + + +def test_invalid_augassign(assert_compile_failed): + code = """ +@public +def foo(x:num): + x += 5 +""" + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), ConstancyViolationException) diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index a4715f4570..d9fd6632ce 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -80,22 +80,6 @@ def assign(self): from .parser import ( make_setter, ) - - if isinstance(self.stmt.targets[0], ast.Subscript) and self.context.in_for_loop: # Check if we are doing assignment of an iteration loop. - raise_exception = False - if isinstance(self.stmt.targets[0].value, ast.Attribute): - list_name = "%s.%s" % (self.stmt.targets[0].value.value.id, self.stmt.targets[0].value.attr) - if list_name in self.context.in_for_loop: - raise_exception = True - - if isinstance(self.stmt.targets[0].value, ast.Name) and \ - self.stmt.targets[0].value.id in self.context.in_for_loop: - list_name = self.stmt.targets[0].value.id - raise_exception = True - - if raise_exception: - raise StructureException("Altering list '%s' which is being iterated!" % list_name, self.stmt) - # Assignment (eg. x[4] = y) if len(self.stmt.targets) != 1: raise StructureException("Assignment statement must have one target", self.stmt) @@ -105,11 +89,8 @@ def assign(self): variable_loc = LLLnode.from_list(pos, typ=sub.typ, location='memory', pos=getpos(self.stmt), annotation=self.stmt.targets[0].id) o = make_setter(variable_loc, sub, 'memory', pos=getpos(self.stmt)) else: - target = Expr.parse_variable_location(self.stmt.targets[0], self.context) - if target.location == 'storage' and self.context.is_constant: - raise ConstancyViolationException("Cannot modify storage inside a constant function!", self.stmt.targets[0]) - if not target.mutable: - raise ConstancyViolationException("Cannot modify function argument", self.stmt.targets[0]) + # Checks to see if assignment is valid + target = self.get_target(self.stmt.targets[0]) o = make_setter(target, sub, target.location, pos=getpos(self.stmt)) o.pos = getpos(self.stmt) return o @@ -170,7 +151,6 @@ def parse_for(self): from .parser import ( parse_body, ) - # Type 0 for, eg. for i in list(): ... if self._is_list_iter(): return self.parse_for_list() @@ -204,9 +184,11 @@ def parse_for(self): start = Expr.parse_value_expr(self.stmt.iter.args[0], self.context) rounds = self.stmt.iter.args[1].right.n varname = self.stmt.target.id - pos = self.context.vars[varname].pos if varname in self.context.forvars else self.context.new_variable(varname, BaseType('num')) - o = LLLnode.from_list(['repeat', pos, start, rounds, parse_body(self.stmt.body, self.context)], typ=None, pos=getpos(self.stmt)) + pos = self.context.new_variable(varname, BaseType('num')) self.context.forvars[varname] = True + o = LLLnode.from_list(['repeat', pos, start, rounds, parse_body(self.stmt.body, self.context)], typ=None, pos=getpos(self.stmt)) + del self.context.vars[varname] + return o def _is_list_iter(self): @@ -239,9 +221,9 @@ def parse_for_list(self): iter_var_type = self.context.vars.get(self.stmt.iter.id).typ if isinstance(self.stmt.iter, ast.Name) else None subtype = iter_list_node.typ.subtype.typ varname = self.stmt.target.id - value_pos = self.context.new_variable(varname, BaseType(subtype)) + value_pos = self.context.new_variable(varname, BaseType(subtype)) i_pos = self.context.new_variable('_index_for_' + varname, BaseType(subtype)) - + self.context.forvars[varname] = True if iter_var_type: # Is a list that is already allocated to memory. self.context.set_in_for_loop(self.stmt.iter.id) # make sure list cannot be altered whilst iterating. iter_var = self.context.vars.get(self.stmt.iter.id) @@ -254,7 +236,6 @@ def parse_for_list(self): ['repeat', i_pos, 0, iter_var.size, body], typ=None, pos=getpos(self.stmt) ) self.context.remove_in_for_loop(self.stmt.iter.id) - return o elif isinstance(self.stmt.iter, ast.List): # List gets defined in the for statement. # Allocate list to memory. count = iter_list_node.typ.count @@ -274,7 +255,6 @@ def parse_for_list(self): setter, ['repeat', i_pos, 0, count, body]], typ=None, pos=getpos(self.stmt) ) - return o elif isinstance(self.stmt.iter, ast.Attribute): # List is contained in storage. count = iter_list_node.typ.count self.context.set_in_for_loop(iter_list_node.annotation) # make sure list cannot be altered whilst iterating. @@ -288,18 +268,20 @@ def parse_for_list(self): ['repeat', i_pos, 0, count, body]], typ=None, pos=getpos(self.stmt) ) self.context.remove_in_for_loop(iter_list_node.annotation) - return o + del self.context.vars[varname] + del self.context.vars['_index_for_' + varname] + return o def aug_assign(self): - target = Expr.parse_variable_location(self.stmt.target, self.context) + target = self.get_target(self.stmt.target) sub = Expr.parse_value_expr(self.stmt.value, self.context) + if isinstance(self.stmt.target, ast.Name) and self.stmt.target.id in self.context.forvars: + raise StructureException("Altering iterator '%s' which is in use!" % target.annotation, self.stmt) if not isinstance(self.stmt.op, (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod)): raise Exception("Unsupported operator for augassign") if not isinstance(target.typ, BaseType): raise TypeMismatchException("Can only use aug-assign operators with simple types!", self.stmt.target) if target.location == 'storage': - if self.context.is_constant: - raise ConstancyViolationException("Cannot modify storage inside a constant function!", self.stmt.target) o = Expr.parse_value_expr(ast.BinOp(left=LLLnode.from_list(['sload', '_stloc'], typ=target.typ, pos=target.pos), right=sub, op=self.stmt.op, lineno=self.stmt.lineno, col_offset=self.stmt.col_offset), self.context) return LLLnode.from_list(['with', '_stloc', target, ['sstore', '_stloc', base_type_conversion(o, o.typ, target.typ)]], typ=None, pos=getpos(self.stmt)) @@ -420,3 +402,28 @@ def increment_dynamic_offset(dynamic_spot): typ=None, pos=getpos(self.stmt)) else: raise TypeMismatchException("Can only return base type!", self.stmt) + + def get_target(self, target): + if isinstance(target, ast.Subscript) and self.context.in_for_loop: # Check if we are doing assignment of an iteration loop. + raise_exception = False + if isinstance(target.value, ast.Attribute): + list_name = "%s.%s" % (target.value.value.id, target.value.attr) + if list_name in self.context.in_for_loop: + raise_exception = True + + if isinstance(target.value, ast.Name) and \ + target.value.id in self.context.in_for_loop: + list_name = target.value.id + raise_exception = True + + if raise_exception: + raise StructureException("Altering list '%s' which is being iterated!" % list_name, self.stmt) + + if isinstance(target, ast.Name) and target.id in self.context.forvars: + raise StructureException("Altering iterator '%s' which is in use!" % target.id, self.stmt) + target = Expr.parse_variable_location(target, self.context) + if target.location == 'storage' and self.context.is_constant: + raise ConstancyViolationException("Cannot modify storage inside a constant function: %s" % target.annotation) + if not target.mutable: + raise ConstancyViolationException("Cannot modify function argument: %s", target.annotation) + return target \ No newline at end of file From 60c0e2f1a455026c41ef1a3f1eb1fd4c6f0b6d49 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sun, 26 Nov 2017 07:31:06 -0700 Subject: [PATCH 097/162] Test for in list invalids --- .../features/iteration/test_for_in_list.py | 96 ++++++++++++++++++- viper/parser/stmt.py | 4 +- 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py index 44a80bb684..e4727f9754 100644 --- a/tests/parser/features/iteration/test_for_in_list.py +++ b/tests/parser/features/iteration/test_for_in_list.py @@ -1,7 +1,7 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ get_contract_with_gas_estimation, get_contract -from viper.exceptions import StructureException +from viper.exceptions import StructureException, VariableDeclarationException def test_basic_for_in_list(): @@ -80,6 +80,44 @@ def data() -> address: assert c.data() == "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" +def test_multiple_for_loops_1(): + code = """ +@public +def foo(x: num): + p = 0 + for i in range(3): + p += i + for i in range(4): + p += i +""" + get_contract_with_gas_estimation(code) + +def test_multiple_for_loops_2(): + code = """ +@public +def foo(x: num): + p = 0 + for i in range(3): + p += i + for i in [1, 2, 3, 4]: + p += i +""" + get_contract_with_gas_estimation(code) + + +def test_multiple_for_loops_3(): + code = """ +@public +def foo(x: num): + p = 0 + for i in [1, 2, 3, 4]: + p += i + for i in [1, 2, 3, 4]: + p += i +""" + get_contract_with_gas_estimation(code) + + def test_basic_for_list_storage_address(): code = """ addresses: address[3] @@ -142,7 +180,7 @@ def i_return(break_count: num) -> decimal: assert c.ret(0) == c.i_return(0) == 0.0001 -def test_altering_list_within_for_loop(assert_compile_failed): +def test_altering_list_within_for_loop_1(assert_compile_failed): code = """ @public def data() -> num: @@ -159,6 +197,18 @@ def data() -> num: assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) +def test_altering_list_within_for_loop_2(assert_compile_failed): + code = """ +@public +def foo(): + s = [1, 2, 3, 4, 5, 6] + count = 0 + for i in s: + s[count] += 1 +""" + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) + + def test_altering_list_within_for_loop_storage(assert_compile_failed): code = """ s: num[6] @@ -179,3 +229,45 @@ def data() -> num: """ assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) + + +def test_invalid_nested_for_loop_1(assert_compile_failed): + code = """ +@public +def foo(x: num): + for i in range(4): + for i in range(5): + pass +""" + assert_compile_failed(lambda: get_contract_with_gas_estimation(code),VariableDeclarationException) + + +def test_invalid_nested_for_loop_2(assert_compile_failed): + code = """ +@public +def foo(x: num): + for i in [1,2]: + for i in [1,2]: + pass +""" + assert_compile_failed(lambda: get_contract_with_gas_estimation(code),VariableDeclarationException) + + +def test_invalid_iterator_assignment_1(assert_compile_failed): + code = """ +@public +def foo(x: num): + for i in [1,2]: + i = 2 +""" + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) + + +def test_invalid_iterator_assignment_2(assert_compile_failed): + code = """ +@public +def foo(x: num): + for i in [1,2]: + i += 2 +""" + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index d9fd6632ce..4c6f8fc564 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -221,7 +221,7 @@ def parse_for_list(self): iter_var_type = self.context.vars.get(self.stmt.iter.id).typ if isinstance(self.stmt.iter, ast.Name) else None subtype = iter_list_node.typ.subtype.typ varname = self.stmt.target.id - value_pos = self.context.new_variable(varname, BaseType(subtype)) + value_pos = self.context.new_variable(varname, BaseType(subtype)) i_pos = self.context.new_variable('_index_for_' + varname, BaseType(subtype)) self.context.forvars[varname] = True if iter_var_type: # Is a list that is already allocated to memory. @@ -426,4 +426,4 @@ def get_target(self, target): raise ConstancyViolationException("Cannot modify storage inside a constant function: %s" % target.annotation) if not target.mutable: raise ConstancyViolationException("Cannot modify function argument: %s", target.annotation) - return target \ No newline at end of file + return target From 928be7822e6f6ec2adc195267f057d01004e1efa Mon Sep 17 00:00:00 2001 From: David Knott Date: Mon, 27 Nov 2017 09:50:22 -0700 Subject: [PATCH 098/162] Remove repetitive error and fix typo --- .../features/iteration/test_for_in_list.py | 24 +++++++++++++++++++ viper/parser/stmt.py | 7 +++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py index e4727f9754..67c9b06bd2 100644 --- a/tests/parser/features/iteration/test_for_in_list.py +++ b/tests/parser/features/iteration/test_for_in_list.py @@ -118,6 +118,30 @@ def foo(x: num): get_contract_with_gas_estimation(code) +def test_multiple_loops_4(): + code = """ +@public +def foo(): + for i in range(10): + pass + for i in range(20): + pass +""" + get_contract_with_gas_estimation(code) + + +def test_using_index_variable_after_loop(): + code = """ +@public +def foo(): + for i in range(10): + pass + i = 100 # create new variable i + i = 200 # look up the variable i and check whether it is in forvars +""" + get_contract_with_gas_estimation(code) + + def test_basic_for_list_storage_address(): code = """ addresses: address[3] diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index 4c6f8fc564..4ac7884104 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -188,7 +188,7 @@ def parse_for(self): self.context.forvars[varname] = True o = LLLnode.from_list(['repeat', pos, start, rounds, parse_body(self.stmt.body, self.context)], typ=None, pos=getpos(self.stmt)) del self.context.vars[varname] - + del self.context.forvars[varname] return o def _is_list_iter(self): @@ -270,13 +270,12 @@ def parse_for_list(self): self.context.remove_in_for_loop(iter_list_node.annotation) del self.context.vars[varname] del self.context.vars['_index_for_' + varname] + del self.context.forvars[varname] return o def aug_assign(self): target = self.get_target(self.stmt.target) sub = Expr.parse_value_expr(self.stmt.value, self.context) - if isinstance(self.stmt.target, ast.Name) and self.stmt.target.id in self.context.forvars: - raise StructureException("Altering iterator '%s' which is in use!" % target.annotation, self.stmt) if not isinstance(self.stmt.op, (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod)): raise Exception("Unsupported operator for augassign") if not isinstance(target.typ, BaseType): @@ -425,5 +424,5 @@ def get_target(self, target): if target.location == 'storage' and self.context.is_constant: raise ConstancyViolationException("Cannot modify storage inside a constant function: %s" % target.annotation) if not target.mutable: - raise ConstancyViolationException("Cannot modify function argument: %s", target.annotation) + raise ConstancyViolationException("Cannot modify function argument: %s" % target.annotation) return target From 9ac49ebfebcd60b5aab23c5f483de77cb8d3a9a8 Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 10 Oct 2017 17:17:55 -0600 Subject: [PATCH 099/162] Begin adding built in erc20 external call functionality --- .../external_contracts/test_erc20_abi.py | 127 ++++++++++++++++++ .../test_external_contract_calls.py | 0 viper/parser/parser.py | 29 +++- viper/parser/stmt.py | 10 +- 4 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 tests/parser/features/external_contracts/test_erc20_abi.py rename tests/parser/features/{ => external_contracts}/test_external_contract_calls.py (100%) diff --git a/tests/parser/features/external_contracts/test_erc20_abi.py b/tests/parser/features/external_contracts/test_erc20_abi.py new file mode 100644 index 0000000000..72e7461a72 --- /dev/null +++ b/tests/parser/features/external_contracts/test_erc20_abi.py @@ -0,0 +1,127 @@ +import pytest +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation, get_contract, assert_tx_failed +from viper.exceptions import StructureException, VariableDeclarationException, InvalidTypeException + +def test_erc20_abi_transfer(): + # TODO: Once erc20 is merged build off that + erc20_code = """ +# Viper Port of MyToken +# THIS CONTRACT HAS NOT BEEN AUDITED! +# ERC20 details at: +# https://theethereum.wiki/w/index.php/ERC20_Token_Standard +# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md + + +# Events of the token. +Transfer: __log__({_from: indexed(address), _to: indexed(address), _value: num256}) +Approval: __log__({_owner: indexed(address), _spender: indexed(address), _value: num256}) + + +# Variables of the token. +name: bytes32 +symbol: bytes32 +totalSupply: num +decimals: num +balances: num[address] +allowed: num[address][address] + + +def __init__(_name: bytes32, _symbol: bytes32, _decimals: num, _initialSupply: num): + + self.name = _name + self.symbol = _symbol + self.decimals = _decimals + self.totalSupply = _initialSupply * 10 ** _decimals + self.balances[msg.sender] = self.totalSupply + + +@constant +def symbol() -> bytes32: + + return self.symbol + + +# What is the balance of a particular account? +@constant +def balanceOf(_owner: address) -> num256: + + return as_num256(self.balances[_owner]) + + +# Return total supply of token. +@constant +def totalSupply() -> num256: + + return as_num256(self.totalSupply) + + +# Send `_value` tokens to `_to` from your account +def transfer(_to: address, _amount: num(num256)) -> bool: + + if self.balances[msg.sender] >= _amount and \ + self.balances[_to] + _amount >= self.balances[_to]: + + self.balances[msg.sender] -= _amount # Subtract from the sender + self.balances[_to] += _amount # Add the same to the recipient + log.Transfer(msg.sender, _to, as_num256(_amount)) # log transfer event. + + return True + else: + return False + + +# Transfer allowed tokens from a specific account to another. +def transferFrom(_from: address, _to: address, _value: num(num256)) -> bool: + + if _value <= self.allowed[_from][msg.sender] and \ + _value <= self.balances[_from]: + + self.balances[_from] -= _value # decrease balance of from address. + self.allowed[_from][msg.sender] -= _value # decrease allowance. + self.balances[_to] += _value # incease balance of to address. + log.Transfer(_from, _to, as_num256(_value)) # log transfer event. + + return True + else: + return False + + +# Allow _spender to withdraw from your account, multiple times, up to the _value amount. +# If this function is called again it overwrites the current allowance with _value. +def approve(_spender: address, _amount: num(num256)) -> bool: + + self.allowed[msg.sender][_spender] = _amount + log.Approval(msg.sender, _spender, as_num256(_amount)) + + return True + + +# Get the allowence an address has to spend anothers' token. +def allowance(_owner: address, _spender: address) -> num256: + + return as_num256(self.allowed[_owner][_spender]) + +""" + + code = """ +token_address: address(ERC20) + +def __init__(token_addr: address): + self.token_address = token_addr + +def transfer(to: address, value: num256): + self.token_address.transfer(to, value) + """ + TOKEN_NAME = "Vipercoin" + TOKEN_SYMBOL = "FANG" + TOKEN_DECIMALS = 18 + TOKEN_INITIAL_SUPPLY = (21 * 10 ** 6) + TOKEN_TOTAL_SUPPLY = TOKEN_INITIAL_SUPPLY * (10 ** TOKEN_DECIMALS) + erc20 = get_contract(erc20_code, args=[TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, TOKEN_INITIAL_SUPPLY]) + c = get_contract(code, args=[erc20.address]) + erc20.transfer(c.address, 10) + assert erc20.balanceOf(c.address) == 10 + c.transfer(t.a1, 10) + assert erc20.balanceOf(c.address) == 0 + assert erc20.balanceOf(t.a1) == 10 diff --git a/tests/parser/features/test_external_contract_calls.py b/tests/parser/features/external_contracts/test_external_contract_calls.py similarity index 100% rename from tests/parser/features/test_external_contract_calls.py rename to tests/parser/features/external_contracts/test_external_contract_calls.py diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 3502062666..74622d2f23 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -169,6 +169,10 @@ def add_contract(code): raise StructureException("Invalid contract reference", item) return _defs +erc20_code = """ +class ERC20(): + def transfer(_to: address, _amount: num256): pass +""" def add_globals_and_events(_defs, _events, _getters, _globals, item): if item.value is not None: @@ -189,6 +193,18 @@ def add_globals_and_events(_defs, _events, _getters, _globals, item): raise StructureException("Global variables must all come before function definitions", item) # If the type declaration is of the form public(), then proceed with # the underlying type but also add getters + elif isinstance(item.annotation, ast.Call) and item.annotation.func.id == "address": + premade_contracts = { + "ERC20": ast.parse(erc20_code).body[0], + } + if len(item.annotation.args) != 1: + raise StructureException("Address expects one arg (the type)") + if item.annotation.args[0].id not in premade_contracts: + raise VariableDeclarationException("Unsupported premade contract declaration", item.annotation.args[0]) + + premade_contract = premade_contracts[item.annotation.args[0].id] + _contracts[item.target.id] = add_contract(premade_contract.body) + _globals[item.target.id] = VariableRecord(item.target.id, len(_globals), BaseType('address'), True) elif isinstance(item.annotation, ast.Call) and item.annotation.func.id == "public": if len(item.annotation.args) != 1: raise StructureException("Public expects one arg (the type)") @@ -200,7 +216,7 @@ def add_globals_and_events(_defs, _events, _getters, _globals, item): _getters[-1].pos = getpos(item) else: _globals[item.target.id] = VariableRecord(item.target.id, len(_globals), parse_type(item.annotation, 'storage'), True) - return _events, _globals, _getters + return _contracts, _events, _globals, _getters # Parse top-level functions and variables @@ -219,7 +235,7 @@ def get_contracts_and_defs_and_globals(code): # Statements of the form: # variable_name: type elif isinstance(item, ast.AnnAssign): - _events, _globals, _getters = add_globals_and_events(_defs, _events, _getters, _globals, item) + _contracts, _events, _globals, _getters = add_globals_and_events(_contracts, _defs, _events, _getters, _globals, item) # Function definitions elif isinstance(item, ast.FunctionDef): _defs.append(item) @@ -470,8 +486,12 @@ def parse_body(code, context): return LLLnode.from_list(['seq'] + o, pos=getpos(code[0]) if code else None) -def external_contract_call_stmt(stmt, context): - contract_name = stmt.func.value.func.id +def declared_external_contract_call_stmt(stmt, context): + contract_name = stmt.func.value.attr + + + +def external_contract_call_stmt(stmt, context, contract_name, contract_address): if contract_name not in context.sigs: raise VariableDeclarationException("Contract not declared yet: %s" % contract_name) method_name = stmt.func.attr @@ -479,7 +499,6 @@ def external_contract_call_stmt(stmt, context): raise VariableDeclarationException("Function not declared yet: %s (reminder: " "function must be declared in the correct contract)" % method_name) sig = context.sigs[contract_name][method_name] - contract_address = parse_expr(stmt.func.value.args[0], context) inargs, inargsize = pack_arguments(sig, [parse_expr(arg, context) for arg in stmt.args], context) sub = ['seq', ['assert', ['extcodesize', ['mload', contract_address]]], ['assert', ['ne', 'address', ['mload', contract_address]]]] diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index a4715f4570..32773b0d72 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -143,7 +143,15 @@ def call(self): return LLLnode.from_list(['assert', ['call', ['gas'], ['address'], 0, inargs, inargsize, 0, 0]], typ=None, pos=getpos(self.stmt)) elif isinstance(self.stmt.func, ast.Attribute) and isinstance(self.stmt.func.value, ast.Call): - return external_contract_call_stmt(self.stmt, self.context) + from .parser import parse_value_expr + contract_name = self.stmt.func.value.func.id + contract_address = parse_value_expr(self.stmt.func.value.args[0], self.context) + return external_contract_call_stmt(self.stmt, self.context, contract_name, contract_address) + elif isinstance(self.stmt.func.value, ast.Attribute) and self.stmt.func.value.attr in self.context.sigs: + from .parser import parse_value_expr + contract_name = self.stmt.func.value.attr + contract_address = parse_value_expr(ast.parse('self.token_address').body[0].value, self.context) + return external_contract_call_stmt(self.stmt, self.context, contract_name, contract_address) elif isinstance(self.stmt.func, ast.Attribute) and self.stmt.func.value.id == 'log': if self.stmt.func.attr not in self.context.sigs['self']: raise VariableDeclarationException("Event not declared yet: %s" % self.stmt.func.attr) From 369b495fe6ebf4bce98f3aa81cf1a9537df3b675 Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 10 Oct 2017 22:53:08 -0600 Subject: [PATCH 100/162] Add implied erc20 token abi fucntionality --- viper/parser/parser.py | 21 +++++++++++---------- viper/premade_contracts.py | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 viper/premade_contracts.py diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 74622d2f23..0b4c2df64e 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -13,6 +13,15 @@ from viper.signatures.event_signature import ( EventSignature ) +<<<<<<< HEAD +======= +from viper.premade_contracts import ( + premade_contracts +) +from viper.functions import ( + dispatch_table, +) +>>>>>>> Add implied erc20 token abi fucntionality from .stmt import Stmt from .expr import Expr from .parser_utils import LLLnode @@ -169,10 +178,7 @@ def add_contract(code): raise StructureException("Invalid contract reference", item) return _defs -erc20_code = """ -class ERC20(): - def transfer(_to: address, _amount: num256): pass -""" + def add_globals_and_events(_defs, _events, _getters, _globals, item): if item.value is not None: @@ -194,9 +200,6 @@ def add_globals_and_events(_defs, _events, _getters, _globals, item): # If the type declaration is of the form public(), then proceed with # the underlying type but also add getters elif isinstance(item.annotation, ast.Call) and item.annotation.func.id == "address": - premade_contracts = { - "ERC20": ast.parse(erc20_code).body[0], - } if len(item.annotation.args) != 1: raise StructureException("Address expects one arg (the type)") if item.annotation.args[0].id not in premade_contracts: @@ -510,8 +513,7 @@ def external_contract_call_stmt(stmt, context, contract_name, contract_address): return o -def external_contract_call_expr(expr, context): - contract_name = expr.func.value.func.id +def external_contract_call_expr(expr, context, contract_name, contract_address): if contract_name not in context.sigs: raise VariableDeclarationException("Contract not declared yet: %s" % contract_name) method_name = expr.func.attr @@ -519,7 +521,6 @@ def external_contract_call_expr(expr, context): raise VariableDeclarationException("Function not declared yet: %s (reminder: " "function must be declared in the correct contract)" % method_name) sig = context.sigs[contract_name][method_name] - contract_address = parse_expr(expr.func.value.args[0], context) inargs, inargsize = pack_arguments(sig, [parse_expr(arg, context) for arg in expr.args], context) output_placeholder = context.new_placeholder(typ=sig.output_type) if isinstance(sig.output_type, BaseType): diff --git a/viper/premade_contracts.py b/viper/premade_contracts.py new file mode 100644 index 0000000000..128fb62c77 --- /dev/null +++ b/viper/premade_contracts.py @@ -0,0 +1,19 @@ +import ast + +erc20 = """ +class ERC20(): + def symbol() -> bytes32: pass + def balanceOf(_owner: address) -> num256: pass + def totalSupply() -> num256: pass + def transfer(_to: address, _amount: num256): pass + def transferFrom(_from: address, _to: address, _value: num(num256)) -> bool: pass + def approve(_spender: address, _amount: num(num256)) -> bool: pass + def allowance(_owner: address, _spender: address) -> num256: pass +""" + +def prepare_code(code): + return ast.parse(code).body[0] + +premade_contracts = { + "ERC20": prepare_code(erc20) +} \ No newline at end of file From 30de77c55fd34bc85c1e60130ed19e1141939343 Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 10 Oct 2017 22:56:54 -0600 Subject: [PATCH 101/162] Add on chain market maker example --- .../market_maker/on_chain_market_maker.v.py | 41 ++++++++ examples/tokens/ERC20.v.py | 95 +++++++++++++++++++ .../test_on_chain_market_maker.py | 77 +++++++++++++++ viper/parser/parser.py | 9 -- viper/premade_contracts.py | 6 +- 5 files changed, 217 insertions(+), 11 deletions(-) create mode 100644 examples/market_maker/on_chain_market_maker.v.py create mode 100644 examples/tokens/ERC20.v.py create mode 100644 tests/examples/market_maker/test_on_chain_market_maker.py diff --git a/examples/market_maker/on_chain_market_maker.v.py b/examples/market_maker/on_chain_market_maker.v.py new file mode 100644 index 0000000000..073774ce15 --- /dev/null +++ b/examples/market_maker/on_chain_market_maker.v.py @@ -0,0 +1,41 @@ +total_eth_qty: public(wei_value) +total_token_qty: public(num) +invariant: public(wei_value) +token_address: address(ERC20) +owner: public(address) + +@payable +def initiate(token_addr: address, token_quantity: num): + assert self.invariant == 0 + self.token_address = token_addr + self.token_address.transferFrom(msg.sender, self, as_num256(token_quantity)) + self.owner = msg.sender + self.total_eth_qty = msg.value + self.total_token_qty = token_quantity + self.invariant = msg.value + assert self.invariant > 0 + +@payable +def eth_to_tokens(): + assert msg.value > 0 + fee = msg.value / 500 + eth_in_purchase = msg.value - fee + new_total_eth = self.total_eth_qty + eth_in_purchase + new_total_tokens = self.invariant / new_total_eth + self.token_address.transfer(msg.sender, + as_num256(self.total_token_qty - new_total_tokens)) + self.total_token_qty = new_total_tokens + +def tokens_to_eth(sell_quantity: num): + assert sell_quantity > 0 + self.token_address.transferFrom(msg.sender, self, as_num256(sell_quantity)) + new_total_tokens = self.total_token_qty + sell_quantity + new_total_eth = self.invariant / new_total_tokens + eth_to_send = self.total_eth_qty - new_total_eth + send(msg.sender, eth_to_send) + self.total_eth_qty = new_total_eth + +def owner_withdraw(): + assert self.owner == msg.sender + self.token_address.transfer(self.owner, as_num256(self.total_token_qty)) + selfdestruct(self.owner) diff --git a/examples/tokens/ERC20.v.py b/examples/tokens/ERC20.v.py new file mode 100644 index 0000000000..a9e1be9fa0 --- /dev/null +++ b/examples/tokens/ERC20.v.py @@ -0,0 +1,95 @@ +# Viper Port of MyToken +# THIS CONTRACT HAS NOT BEEN AUDITED! +# ERC20 details at: +# https://theethereum.wiki/w/index.php/ERC20_Token_Standard +# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md + + +# Events of the token. +Transfer: __log__({_from: indexed(address), _to: indexed(address), _value: num256}) +Approval: __log__({_owner: indexed(address), _spender: indexed(address), _value: num256}) + + +# Variables of the token. +name: bytes32 +symbol: bytes32 +totalSupply: num +decimals: num +balances: num[address] +allowed: num[address][address] + + +def __init__(_name: bytes32, _symbol: bytes32, _decimals: num, _initialSupply: num): + + self.name = _name + self.symbol = _symbol + self.decimals = _decimals + self.totalSupply = _initialSupply * 10 ** _decimals + self.balances[msg.sender] = self.totalSupply + + +@constant +def symbol() -> bytes32: + + return self.symbol + + +# What is the balance of a particular account? +@constant +def balanceOf(_owner: address) -> num256: + + return as_num256(self.balances[_owner]) + + +# Return total supply of token. +@constant +def totalSupply() -> num256: + + return as_num256(self.totalSupply) + + +# Send `_value` tokens to `_to` from your account +def transfer(_to: address, _amount: num(num256)) -> bool: + + if self.balances[msg.sender] >= _amount and \ + self.balances[_to] + _amount >= self.balances[_to]: + + self.balances[msg.sender] -= _amount # Subtract from the sender + self.balances[_to] += _amount # Add the same to the recipient + log.Transfer(msg.sender, _to, as_num256(_amount)) # log transfer event. + + return True + else: + return False + + +# Transfer allowed tokens from a specific account to another. +def transferFrom(_from: address, _to: address, _value: num(num256)) -> bool: + + if _value <= self.allowed[_from][msg.sender] and \ + _value <= self.balances[_from]: + + self.balances[_from] -= _value # decrease balance of from address. + self.allowed[_from][msg.sender] -= _value # decrease allowance. + self.balances[_to] += _value # incease balance of to address. + log.Transfer(_from, _to, as_num256(_value)) # log transfer event. + + return True + else: + return False + + +# Allow _spender to withdraw from your account, multiple times, up to the _value amount. +# If this function is called again it overwrites the current allowance with _value. +def approve(_spender: address, _amount: num(num256)) -> bool: + + self.allowed[msg.sender][_spender] = _amount + log.Approval(msg.sender, _spender, as_num256(_amount)) + + return True + + +# Get the allowence an address has to spend anothers' token. +def allowance(_owner: address, _spender: address) -> num256: + + return as_num256(self.allowed[_owner][_spender]) diff --git a/tests/examples/market_maker/test_on_chain_market_maker.py b/tests/examples/market_maker/test_on_chain_market_maker.py new file mode 100644 index 0000000000..fc2bdf9b9e --- /dev/null +++ b/tests/examples/market_maker/test_on_chain_market_maker.py @@ -0,0 +1,77 @@ +import pytest +from viper import compiler +from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ + get_contract_with_gas_estimation, get_contract, assert_tx_failed +from viper.exceptions import StructureException, VariableDeclarationException, InvalidTypeException + +@pytest.fixture +def market_maker(): + t.languages['viper'] = compiler.Compiler() + contract_code = open('examples/market_maker/on_chain_market_maker.v.py').read() + return s.contract(contract_code, language='viper') + +TOKEN_NAME = "Vipercoin" +TOKEN_SYMBOL = "FANG" +TOKEN_DECIMALS = 18 +TOKEN_INITIAL_SUPPLY = (21 * 10 ** 6) +TOKEN_TOTAL_SUPPLY = TOKEN_INITIAL_SUPPLY * (10 ** TOKEN_DECIMALS) + +@pytest.fixture +def erc20(): + t.languages['viper'] = compiler.Compiler() + contract_code = open('examples/tokens/ERC20.v.py').read() + return s.contract(contract_code, language='viper', args=[TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, TOKEN_INITIAL_SUPPLY]) + + +def test_initial_statet(market_maker): + assert market_maker.get_total_eth_qty() == 0 + assert market_maker.get_total_token_qty() == 0 + assert market_maker.get_invariant() == 0 + assert u.remove_0x_head(market_maker.get_owner()) == '0000000000000000000000000000000000000000' + + +def test_initiate(market_maker, erc20, assert_tx_failed): + erc20.approve(market_maker.address, 2*10**18) + market_maker.initiate(erc20.address, 1*10**18, value=2*10**18) + assert market_maker.get_total_eth_qty() == 2*10**18 + assert market_maker.get_total_token_qty() == 1*10**18 + assert market_maker.get_invariant() == 2*10**18 + assert u.remove_0x_head(market_maker.get_owner()) == t.a0.hex() + t.s = s + # Initiate cannot be called twice + assert_tx_failed(t, lambda: market_maker.initiate(erc20.address, 1*10**18, value=2*10**18)) + + +def test_eth_to_tokens(market_maker, erc20): + erc20.approve(market_maker.address, 2*10**18) + market_maker.initiate(erc20.address, 1*10**18, value=2*10**18) + assert erc20.balanceOf(market_maker.address) == 1000000000000000000 + assert erc20.balanceOf(t.a1) == 0 + market_maker.eth_to_tokens(value=100, sender=t.k1) + assert erc20.balanceOf(market_maker.address) == 0 + assert erc20.balanceOf(t.a1) == 1000000000000000000 + assert market_maker.get_total_token_qty() == 0 + + +def test_tokens_to_eth(market_maker, erc20): + erc20.approve(market_maker.address, 2*10**18) + market_maker.initiate(erc20.address, 1*10**18, value=2*10**18) + assert s.head_state.get_balance(market_maker.address) == 2000000000000000000 + assert s.head_state.get_balance(t.a1) == 999999999999999999999900 + market_maker.tokens_to_eth(100, sender=t.k1) + assert s.head_state.get_balance(market_maker.address) == 1 + assert s.head_state.get_balance(t.a1) == 1000001999999999999999899 + assert market_maker.get_total_eth_qty() == 1 + + +def test_owner_withdraw(market_maker, erc20, assert_tx_failed): + erc20.approve(market_maker.address, 2*10**18) + market_maker.initiate(erc20.address, 1*10**18, value=2*10**18) + assert s.head_state.get_balance(t.a0) == 999992000000000000000000 + assert erc20.balanceOf(t.a0) == 20999999000000000000000000 + t.s = s + # Only owner can call owner_withdraw + assert_tx_failed(t, lambda: market_maker.owner_withdraw(sender=t.k1)) + market_maker.owner_withdraw() + assert s.head_state.get_balance(t.a0) == 999994000000000000000000 + assert erc20.balanceOf(t.a0) == 21000000000000000000000000 diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 0b4c2df64e..8f5f022906 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -13,15 +13,12 @@ from viper.signatures.event_signature import ( EventSignature ) -<<<<<<< HEAD -======= from viper.premade_contracts import ( premade_contracts ) from viper.functions import ( dispatch_table, ) ->>>>>>> Add implied erc20 token abi fucntionality from .stmt import Stmt from .expr import Expr from .parser_utils import LLLnode @@ -204,7 +201,6 @@ def add_globals_and_events(_defs, _events, _getters, _globals, item): raise StructureException("Address expects one arg (the type)") if item.annotation.args[0].id not in premade_contracts: raise VariableDeclarationException("Unsupported premade contract declaration", item.annotation.args[0]) - premade_contract = premade_contracts[item.annotation.args[0].id] _contracts[item.target.id] = add_contract(premade_contract.body) _globals[item.target.id] = VariableRecord(item.target.id, len(_globals), BaseType('address'), True) @@ -489,11 +485,6 @@ def parse_body(code, context): return LLLnode.from_list(['seq'] + o, pos=getpos(code[0]) if code else None) -def declared_external_contract_call_stmt(stmt, context): - contract_name = stmt.func.value.attr - - - def external_contract_call_stmt(stmt, context, contract_name, contract_address): if contract_name not in context.sigs: raise VariableDeclarationException("Contract not declared yet: %s" % contract_name) diff --git a/viper/premade_contracts.py b/viper/premade_contracts.py index 128fb62c77..a35c38b5a2 100644 --- a/viper/premade_contracts.py +++ b/viper/premade_contracts.py @@ -5,15 +5,17 @@ class ERC20(): def symbol() -> bytes32: pass def balanceOf(_owner: address) -> num256: pass def totalSupply() -> num256: pass - def transfer(_to: address, _amount: num256): pass + def transfer(_to: address, _amount: num256) -> bool: pass def transferFrom(_from: address, _to: address, _value: num(num256)) -> bool: pass def approve(_spender: address, _amount: num(num256)) -> bool: pass def allowance(_owner: address, _spender: address) -> num256: pass """ + def prepare_code(code): return ast.parse(code).body[0] + premade_contracts = { "ERC20": prepare_code(erc20) -} \ No newline at end of file +} From f0cb7cab8dea667e86b2ad86ee238cc8a33705f3 Mon Sep 17 00:00:00 2001 From: David Knott Date: Thu, 26 Oct 2017 16:48:55 -0600 Subject: [PATCH 102/162] Test implied erc20 abi functionality --- .../market_maker/on_chain_market_maker.v.py | 2 - .../external_contracts/test_erc20_abi.py | 179 +++++++----------- viper/parser/expr.py | 8 +- viper/parser/stmt.py | 6 +- 4 files changed, 82 insertions(+), 113 deletions(-) diff --git a/examples/market_maker/on_chain_market_maker.v.py b/examples/market_maker/on_chain_market_maker.v.py index 073774ce15..6f30e80c56 100644 --- a/examples/market_maker/on_chain_market_maker.v.py +++ b/examples/market_maker/on_chain_market_maker.v.py @@ -17,7 +17,6 @@ def initiate(token_addr: address, token_quantity: num): @payable def eth_to_tokens(): - assert msg.value > 0 fee = msg.value / 500 eth_in_purchase = msg.value - fee new_total_eth = self.total_eth_qty + eth_in_purchase @@ -27,7 +26,6 @@ def eth_to_tokens(): self.total_token_qty = new_total_tokens def tokens_to_eth(sell_quantity: num): - assert sell_quantity > 0 self.token_address.transferFrom(msg.sender, self, as_num256(sell_quantity)) new_total_tokens = self.total_token_qty + sell_quantity new_total_eth = self.invariant / new_total_tokens diff --git a/tests/parser/features/external_contracts/test_erc20_abi.py b/tests/parser/features/external_contracts/test_erc20_abi.py index 72e7461a72..5729c69629 100644 --- a/tests/parser/features/external_contracts/test_erc20_abi.py +++ b/tests/parser/features/external_contracts/test_erc20_abi.py @@ -1,127 +1,94 @@ import pytest +from ethereum.abi import ValueOutOfBounds from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ get_contract_with_gas_estimation, get_contract, assert_tx_failed from viper.exceptions import StructureException, VariableDeclarationException, InvalidTypeException -def test_erc20_abi_transfer(): - # TODO: Once erc20 is merged build off that - erc20_code = """ -# Viper Port of MyToken -# THIS CONTRACT HAS NOT BEEN AUDITED! -# ERC20 details at: -# https://theethereum.wiki/w/index.php/ERC20_Token_Standard -# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md +TOKEN_NAME = "Vipercoin" +TOKEN_SYMBOL = "FANG" +TOKEN_DECIMALS = 18 +TOKEN_INITIAL_SUPPLY = (21 * 10 ** 6) +TOKEN_TOTAL_SUPPLY = TOKEN_INITIAL_SUPPLY * (10 ** TOKEN_DECIMALS) +@pytest.fixture +def erc20(): + erc20_code = open('examples/tokens/ERC20.v.py').read() + return get_contract(erc20_code, args=[TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, TOKEN_INITIAL_SUPPLY]) -# Events of the token. -Transfer: __log__({_from: indexed(address), _to: indexed(address), _value: num256}) -Approval: __log__({_owner: indexed(address), _spender: indexed(address), _value: num256}) - - -# Variables of the token. -name: bytes32 -symbol: bytes32 -totalSupply: num -decimals: num -balances: num[address] -allowed: num[address][address] - - -def __init__(_name: bytes32, _symbol: bytes32, _decimals: num, _initialSupply: num): - - self.name = _name - self.symbol = _symbol - self.decimals = _decimals - self.totalSupply = _initialSupply * 10 ** _decimals - self.balances[msg.sender] = self.totalSupply +@pytest.fixture +def erc20_caller(erc20): + erc20_caller_code = """ +token_address: address(ERC20) +def __init__(token_addr: address): + self.token_address = token_addr -@constant def symbol() -> bytes32: + return self.token_address.symbol() - return self.symbol - - -# What is the balance of a particular account? -@constant def balanceOf(_owner: address) -> num256: + return self.token_address.balanceOf(_owner) - return as_num256(self.balances[_owner]) - - -# Return total supply of token. -@constant def totalSupply() -> num256: + return self.token_address.totalSupply() - return as_num256(self.totalSupply) - - -# Send `_value` tokens to `_to` from your account -def transfer(_to: address, _amount: num(num256)) -> bool: - - if self.balances[msg.sender] >= _amount and \ - self.balances[_to] + _amount >= self.balances[_to]: - - self.balances[msg.sender] -= _amount # Subtract from the sender - self.balances[_to] += _amount # Add the same to the recipient - log.Transfer(msg.sender, _to, as_num256(_amount)) # log transfer event. - - return True - else: - return False - +def transfer(_to: address, _value: num256) -> bool: + return self.token_address.transfer(_to, _value) -# Transfer allowed tokens from a specific account to another. def transferFrom(_from: address, _to: address, _value: num(num256)) -> bool: + return self.token_address.transferFrom(_from, _to, _value) - if _value <= self.allowed[_from][msg.sender] and \ - _value <= self.balances[_from]: - - self.balances[_from] -= _value # decrease balance of from address. - self.allowed[_from][msg.sender] -= _value # decrease allowance. - self.balances[_to] += _value # incease balance of to address. - log.Transfer(_from, _to, as_num256(_value)) # log transfer event. - - return True - else: - return False - - -# Allow _spender to withdraw from your account, multiple times, up to the _value amount. -# If this function is called again it overwrites the current allowance with _value. -def approve(_spender: address, _amount: num(num256)) -> bool: - - self.allowed[msg.sender][_spender] = _amount - log.Approval(msg.sender, _spender, as_num256(_amount)) - - return True - - -# Get the allowence an address has to spend anothers' token. def allowance(_owner: address, _spender: address) -> num256: - - return as_num256(self.allowed[_owner][_spender]) - -""" - - code = """ -token_address: address(ERC20) - -def __init__(token_addr: address): - self.token_address = token_addr - -def transfer(to: address, value: num256): - self.token_address.transfer(to, value) + return self.token_address.allowance(_owner, _spender) """ - TOKEN_NAME = "Vipercoin" - TOKEN_SYMBOL = "FANG" - TOKEN_DECIMALS = 18 - TOKEN_INITIAL_SUPPLY = (21 * 10 ** 6) - TOKEN_TOTAL_SUPPLY = TOKEN_INITIAL_SUPPLY * (10 ** TOKEN_DECIMALS) - erc20 = get_contract(erc20_code, args=[TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, TOKEN_INITIAL_SUPPLY]) - c = get_contract(code, args=[erc20.address]) - erc20.transfer(c.address, 10) - assert erc20.balanceOf(c.address) == 10 - c.transfer(t.a1, 10) - assert erc20.balanceOf(c.address) == 0 + return get_contract(erc20_caller_code, args=[erc20.address]) + +def pad_bytes32(instr): + """ Pad a string \x00 bytes to return correct bytes32 representation. """ + bstr = instr.encode() + return bstr + (32 - len(bstr)) * b'\x00' + +def test_initial_state(erc20_caller): + assert erc20_caller.totalSupply() == TOKEN_TOTAL_SUPPLY == erc20_caller.balanceOf(t.a0) + assert erc20_caller.balanceOf(t.a1) == 0 + assert erc20_caller.symbol() == pad_bytes32(TOKEN_SYMBOL) + +def test_call_transfer(erc20, erc20_caller, assert_tx_failed): + + # Basic transfer. + erc20.transfer(erc20_caller.address, 10) + assert erc20.balanceOf(erc20_caller.address) == 10 + erc20_caller.transfer(t.a1, 10) + assert erc20.balanceOf(erc20_caller.address) == 0 assert erc20.balanceOf(t.a1) == 10 + + + # Some edge cases: + + # more than allowed + assert erc20_caller.transfer(t.a1, TOKEN_TOTAL_SUPPLY) is False + + t.s = s + # Negative transfer value. + assert_tx_failed( + tester=t, + function_to_test=lambda: erc20_caller.transfer(t.a1, -1), + exception=ValueOutOfBounds + ) + +def test_caller_approve_allowance(erc20, erc20_caller): + assert erc20_caller.allowance(erc20.address, erc20_caller.address) == 0 + assert erc20.approve(erc20_caller.address, 10) == True + assert erc20_caller.allowance(t.a0, erc20_caller.address) == 10 + +def test_caller_tranfer_from(erc20, erc20_caller, assert_tx_failed): + # Cannot transfer tokens that are unavailable + erc20_caller.transferFrom(t.a0, erc20_caller.address, 10) + assert erc20.balanceOf(erc20_caller.address) == 0 + assert erc20.approve(erc20_caller.address, 10) == True + erc20_caller.transferFrom(t.a0, erc20_caller.address, 5) + assert erc20.balanceOf(erc20_caller.address) == 5 + assert erc20_caller.allowance(t.a0, erc20_caller.address) == 5 + erc20_caller.transferFrom(t.a0, erc20_caller.address, 3) + assert erc20.balanceOf(erc20_caller.address) == 8 + assert erc20_caller.allowance(t.a0, erc20_caller.address) == 2 diff --git a/viper/parser/expr.py b/viper/parser/expr.py index 727cf40e26..b690397e34 100644 --- a/viper/parser/expr.py +++ b/viper/parser/expr.py @@ -458,7 +458,13 @@ def call(self): o.gas += sig.gas return o elif isinstance(self.expr.func, ast.Attribute) and isinstance(self.expr.func.value, ast.Call): - return external_contract_call_expr(self.expr, self.context) + contract_name = self.expr.func.value.func.id + contract_address = Expr.parse_value_expr(self.expr.func.value.args[0], self.context) + return external_contract_call_expr(self.expr, self.context, contract_name, contract_address) + elif isinstance(self.expr.func.value, ast.Attribute) and self.expr.func.value.attr in self.context.sigs: + contract_name = self.expr.func.value.attr + contract_address = Expr.parse_value_expr(ast.parse('self.token_address').body[0].value, self.context) + return external_contract_call_expr(self.expr, self.context, contract_name, contract_address) else: raise StructureException("Unsupported operator: %r" % ast.dump(self.expr), self.expr) diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index 32773b0d72..b084fb4823 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -143,14 +143,12 @@ def call(self): return LLLnode.from_list(['assert', ['call', ['gas'], ['address'], 0, inargs, inargsize, 0, 0]], typ=None, pos=getpos(self.stmt)) elif isinstance(self.stmt.func, ast.Attribute) and isinstance(self.stmt.func.value, ast.Call): - from .parser import parse_value_expr contract_name = self.stmt.func.value.func.id - contract_address = parse_value_expr(self.stmt.func.value.args[0], self.context) + contract_address = Expr.parse_value_expr(self.stmt.func.value.args[0], self.context) return external_contract_call_stmt(self.stmt, self.context, contract_name, contract_address) elif isinstance(self.stmt.func.value, ast.Attribute) and self.stmt.func.value.attr in self.context.sigs: - from .parser import parse_value_expr contract_name = self.stmt.func.value.attr - contract_address = parse_value_expr(ast.parse('self.token_address').body[0].value, self.context) + contract_address = Expr.parse_value_expr(ast.parse('self.token_address').body[0].value, self.context) return external_contract_call_stmt(self.stmt, self.context, contract_name, contract_address) elif isinstance(self.stmt.func, ast.Attribute) and self.stmt.func.value.id == 'log': if self.stmt.func.attr not in self.context.sigs['self']: From 1394fa3d1587fa070878cf8d05ca6b311496a5d9 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sat, 25 Nov 2017 10:27:03 -0700 Subject: [PATCH 103/162] Update on chain market maker and erc20 abi --- .../market_maker/on_chain_market_maker.v.py | 6 ++++- .../test_on_chain_market_maker.py | 10 ++++----- .../external_contracts/test_erc20_abi.py | 20 ++++++++++------- .../test_external_contract_calls.py | 10 +-------- viper/parser/parser.py | 22 ++++++++----------- 5 files changed, 32 insertions(+), 36 deletions(-) diff --git a/examples/market_maker/on_chain_market_maker.v.py b/examples/market_maker/on_chain_market_maker.v.py index 6f30e80c56..b81f014c9d 100644 --- a/examples/market_maker/on_chain_market_maker.v.py +++ b/examples/market_maker/on_chain_market_maker.v.py @@ -4,6 +4,7 @@ token_address: address(ERC20) owner: public(address) +@public @payable def initiate(token_addr: address, token_quantity: num): assert self.invariant == 0 @@ -15,6 +16,7 @@ def initiate(token_addr: address, token_quantity: num): self.invariant = msg.value assert self.invariant > 0 +@public @payable def eth_to_tokens(): fee = msg.value / 500 @@ -25,14 +27,16 @@ def eth_to_tokens(): as_num256(self.total_token_qty - new_total_tokens)) self.total_token_qty = new_total_tokens +@public def tokens_to_eth(sell_quantity: num): - self.token_address.transferFrom(msg.sender, self, as_num256(sell_quantity)) + # self.token_address.transferFrom(msg.sender, self, as_num256(sell_quantity)) new_total_tokens = self.total_token_qty + sell_quantity new_total_eth = self.invariant / new_total_tokens eth_to_send = self.total_eth_qty - new_total_eth send(msg.sender, eth_to_send) self.total_eth_qty = new_total_eth +@public def owner_withdraw(): assert self.owner == msg.sender self.token_address.transfer(self.owner, as_num256(self.total_token_qty)) diff --git a/tests/examples/market_maker/test_on_chain_market_maker.py b/tests/examples/market_maker/test_on_chain_market_maker.py index fc2bdf9b9e..5621d47a6e 100644 --- a/tests/examples/market_maker/test_on_chain_market_maker.py +++ b/tests/examples/market_maker/test_on_chain_market_maker.py @@ -1,8 +1,7 @@ import pytest from viper import compiler from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract, assert_tx_failed -from viper.exceptions import StructureException, VariableDeclarationException, InvalidTypeException + get_contract_with_gas_estimation, get_contract @pytest.fixture def market_maker(): @@ -19,7 +18,7 @@ def market_maker(): @pytest.fixture def erc20(): t.languages['viper'] = compiler.Compiler() - contract_code = open('examples/tokens/ERC20.v.py').read() + contract_code = open('examples/tokens/vipercoin.v.py').read() return s.contract(contract_code, language='viper', args=[TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, TOKEN_INITIAL_SUPPLY]) @@ -39,7 +38,7 @@ def test_initiate(market_maker, erc20, assert_tx_failed): assert u.remove_0x_head(market_maker.get_owner()) == t.a0.hex() t.s = s # Initiate cannot be called twice - assert_tx_failed(t, lambda: market_maker.initiate(erc20.address, 1*10**18, value=2*10**18)) + assert_tx_failed(lambda: market_maker.initiate(erc20.address, 1*10**18, value=2*10**18)) def test_eth_to_tokens(market_maker, erc20): @@ -58,6 +57,7 @@ def test_tokens_to_eth(market_maker, erc20): market_maker.initiate(erc20.address, 1*10**18, value=2*10**18) assert s.head_state.get_balance(market_maker.address) == 2000000000000000000 assert s.head_state.get_balance(t.a1) == 999999999999999999999900 + erc20.approve(market_maker.address, 1*10**18, sender=t.k1) market_maker.tokens_to_eth(100, sender=t.k1) assert s.head_state.get_balance(market_maker.address) == 1 assert s.head_state.get_balance(t.a1) == 1000001999999999999999899 @@ -71,7 +71,7 @@ def test_owner_withdraw(market_maker, erc20, assert_tx_failed): assert erc20.balanceOf(t.a0) == 20999999000000000000000000 t.s = s # Only owner can call owner_withdraw - assert_tx_failed(t, lambda: market_maker.owner_withdraw(sender=t.k1)) + assert_tx_failed(lambda: market_maker.owner_withdraw(sender=t.k1)) market_maker.owner_withdraw() assert s.head_state.get_balance(t.a0) == 999994000000000000000000 assert erc20.balanceOf(t.a0) == 21000000000000000000000000 diff --git a/tests/parser/features/external_contracts/test_erc20_abi.py b/tests/parser/features/external_contracts/test_erc20_abi.py index 5729c69629..e78a0fe0ed 100644 --- a/tests/parser/features/external_contracts/test_erc20_abi.py +++ b/tests/parser/features/external_contracts/test_erc20_abi.py @@ -1,8 +1,7 @@ import pytest from ethereum.abi import ValueOutOfBounds from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract, assert_tx_failed -from viper.exceptions import StructureException, VariableDeclarationException, InvalidTypeException + get_contract_with_gas_estimation, get_contract TOKEN_NAME = "Vipercoin" TOKEN_SYMBOL = "FANG" @@ -12,7 +11,7 @@ @pytest.fixture def erc20(): - erc20_code = open('examples/tokens/ERC20.v.py').read() + erc20_code = open('examples/tokens/vipercoin.v.py').read() return get_contract(erc20_code, args=[TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, TOKEN_INITIAL_SUPPLY]) @pytest.fixture @@ -20,29 +19,37 @@ def erc20_caller(erc20): erc20_caller_code = """ token_address: address(ERC20) +@public def __init__(token_addr: address): self.token_address = token_addr +@public def symbol() -> bytes32: return self.token_address.symbol() +@public def balanceOf(_owner: address) -> num256: return self.token_address.balanceOf(_owner) +@public def totalSupply() -> num256: return self.token_address.totalSupply() +@public def transfer(_to: address, _value: num256) -> bool: return self.token_address.transfer(_to, _value) +@public def transferFrom(_from: address, _to: address, _value: num(num256)) -> bool: return self.token_address.transferFrom(_from, _to, _value) +@public def allowance(_owner: address, _spender: address) -> num256: return self.token_address.allowance(_owner, _spender) """ return get_contract(erc20_caller_code, args=[erc20.address]) + def pad_bytes32(instr): """ Pad a string \x00 bytes to return correct bytes32 representation. """ bstr = instr.encode() @@ -63,15 +70,12 @@ def test_call_transfer(erc20, erc20_caller, assert_tx_failed): assert erc20.balanceOf(t.a1) == 10 - # Some edge cases: - # more than allowed - assert erc20_caller.transfer(t.a1, TOKEN_TOTAL_SUPPLY) is False + assert_tx_failed(lambda: erc20_caller.transfer(t.a1, TOKEN_TOTAL_SUPPLY)) t.s = s # Negative transfer value. assert_tx_failed( - tester=t, function_to_test=lambda: erc20_caller.transfer(t.a1, -1), exception=ValueOutOfBounds ) @@ -83,7 +87,7 @@ def test_caller_approve_allowance(erc20, erc20_caller): def test_caller_tranfer_from(erc20, erc20_caller, assert_tx_failed): # Cannot transfer tokens that are unavailable - erc20_caller.transferFrom(t.a0, erc20_caller.address, 10) + assert_tx_failed(lambda: erc20_caller.transferFrom(t.a0, erc20_caller.address, 10)) assert erc20.balanceOf(erc20_caller.address) == 0 assert erc20.approve(erc20_caller.address, 10) == True erc20_caller.transferFrom(t.a0, erc20_caller.address, 5) diff --git a/tests/parser/features/external_contracts/test_external_contract_calls.py b/tests/parser/features/external_contracts/test_external_contract_calls.py index 7bc7274a58..21ad245a7d 100644 --- a/tests/parser/features/external_contracts/test_external_contract_calls.py +++ b/tests/parser/features/external_contracts/test_external_contract_calls.py @@ -1,6 +1,6 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract, assert_tx_failed + get_contract_with_gas_estimation, get_contract from viper.exceptions import StructureException, VariableDeclarationException, InvalidTypeException def test_external_contract_calls(): @@ -275,7 +275,6 @@ def _expr(x: address) -> num: return Bar(x).bar() """ - t.s = t.Chain() c1 = get_contract(contract_1) c2 = get_contract(contract_2) @@ -303,7 +302,6 @@ def foo(x: address) -> num: c1 = get_contract(contract_1) c2 = get_contract(contract_2) - t.s = s assert c2.foo(c1.address) == 1 assert_tx_failed(lambda: c2.foo(t.a1)) @@ -321,7 +319,6 @@ class Bar(): def __init__(): pass """ - t.s = t.Chain() assert_tx_failed(lambda: get_contract(contract), exception = StructureException) @@ -331,7 +328,6 @@ def test_invalid_contract_reference_call(assert_tx_failed): def bar(arg1: address, arg2: num) -> num: return Foo(arg1).foo(arg2) """ - t.s = t.Chain() assert_tx_failed(lambda: get_contract(contract), exception = VariableDeclarationException) @@ -344,7 +340,6 @@ def foo(arg2: num) -> invalid: pass def bar(arg1: address, arg2: num) -> num: return Foo(arg1).foo(arg2) """ - t.s = t.Chain() assert_tx_failed(lambda: get_contract(contract), exception = InvalidTypeException) @@ -356,7 +351,6 @@ def test_external_contracts_must_be_declared_first_1(assert_tx_failed): class Foo(): def foo(arg2: num) -> num: pass """ - t.s = t.Chain() assert_tx_failed(lambda: get_contract(contract), exception = StructureException) @@ -368,7 +362,6 @@ def test_external_contracts_must_be_declared_first_2(assert_tx_failed): class Foo(): def foo(arg2: num) -> num: pass """ - t.s = t.Chain() assert_tx_failed(lambda: get_contract(contract), exception = StructureException) @@ -381,5 +374,4 @@ def foo() -> num: class Foo(): def foo(arg2: num) -> num: pass """ - t.s = t.Chain() assert_tx_failed(lambda: get_contract(contract), exception = StructureException) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 8f5f022906..507ee2c006 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -16,9 +16,6 @@ from viper.premade_contracts import ( premade_contracts ) -from viper.functions import ( - dispatch_table, -) from .stmt import Stmt from .expr import Expr from .parser_utils import LLLnode @@ -176,8 +173,7 @@ def add_contract(code): return _defs - -def add_globals_and_events(_defs, _events, _getters, _globals, item): +def add_globals_and_events(_contracts, _defs, _events, _getters, _globals, item): if item.value is not None: raise StructureException('May not assign value whilst defining type', item) elif isinstance(item.annotation, ast.Call) and item.annotation.func.id == "__log__": @@ -494,12 +490,12 @@ def external_contract_call_stmt(stmt, context, contract_name, contract_address): "function must be declared in the correct contract)" % method_name) sig = context.sigs[contract_name][method_name] inargs, inargsize = pack_arguments(sig, [parse_expr(arg, context) for arg in stmt.args], context) - sub = ['seq', ['assert', ['extcodesize', ['mload', contract_address]]], - ['assert', ['ne', 'address', ['mload', contract_address]]]] + sub = ['seq', ['assert', ['extcodesize', contract_address]], + ['assert', ['ne', 'address', contract_address]]] if context.is_constant: - sub.append(['assert', ['staticcall', 'gas', ['mload', contract_address], inargs, inargsize, 0, 0]]) + sub.append(['assert', ['staticcall', 'gas', contract_address, inargs, inargsize, 0, 0]]) else: - sub.append(['assert', ['call', 'gas', ['mload', contract_address], 0, inargs, inargsize, 0, 0]]) + sub.append(['assert', ['call', 'gas', contract_address, 0, inargs, inargsize, 0, 0]]) o = LLLnode.from_list(sub, typ=sig.output_type, location='memory', pos=getpos(stmt)) return o @@ -520,13 +516,13 @@ def external_contract_call_expr(expr, context, contract_name, contract_address): returner = output_placeholder + 32 else: raise TypeMismatchException("Invalid output type: %r" % sig.output_type, expr) - sub = ['seq', ['assert', ['extcodesize', ['mload', contract_address]]], - ['assert', ['ne', 'address', ['mload', contract_address]]]] + sub = ['seq', ['assert', ['extcodesize', contract_address]], + ['assert', ['ne', 'address', contract_address]]] if context.is_constant: - sub.append(['assert', ['staticcall', 'gas', ['mload', contract_address], inargs, inargsize, + sub.append(['assert', ['staticcall', 'gas', contract_address, inargs, inargsize, output_placeholder, get_size_of_type(sig.output_type) * 32]]) else: - sub.append(['assert', ['call', 'gas', ['mload', contract_address], 0, inargs, inargsize, + sub.append(['assert', ['call', 'gas', contract_address, 0, inargs, inargsize, output_placeholder, get_size_of_type(sig.output_type) * 32]]) sub.extend([0, returner]) o = LLLnode.from_list(sub, typ=sig.output_type, location='memory', pos=getpos(expr)) From 5d9c65a2108f6c72254198ff840ed1c957d62162 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Wed, 29 Nov 2017 14:23:09 +0200 Subject: [PATCH 104/162] Change @internal decorator to @private decorator. --- docs/index.rst | 3 +- docs/old_readme.txt | 28 +++++++++---------- docs/structure-of-a-contract.rst | 2 +- .../{test_internal.py => test_private.py} | 10 +++---- .../parser/features/decorators/test_public.py | 5 +--- tests/parser/syntax/test_invalids.py | 2 +- viper/function_signature.py | 20 ++++++------- viper/parser/parser.py | 4 +-- 8 files changed, 36 insertions(+), 38 deletions(-) rename tests/parser/features/decorators/{test_internal.py => test_private.py} (64%) diff --git a/docs/index.rst b/docs/index.rst index f36b22be53..c3965cf37f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,10 +48,11 @@ Following the principles and goals, Viper **does not** provide the following fea Compatibility-breaking Changelog ******************************** +* **2017.11.29**: ``@internal`` renamed to ``@private```. * **2017.11.15**: Functions require either ``@internal`` or ``@public`` decorators. * **2017.07.25**: The ``def foo() -> num(const): ...`` syntax no longer works; you now need to do ``def foo() -> num: ...`` with a ``@constant`` decorator on the previous line. * **2017.07.25**: Functions without a ``@payable`` decorator now fail when called with nonzero wei. -* **2017.07.25**: A function can only call functions that are declared above it (that is, A can call B only if B appears earlier in the code than A does). This was introduced +* **2017.07.25**: A function can only call functions that are declared above it (that is, A can call B only if B appears earlier in the code than A does). This was introduced to prevent infinite looping through recursion. ******** diff --git a/docs/old_readme.txt b/docs/old_readme.txt index 13650c23fd..6463d54e22 100644 --- a/docs/old_readme.txt +++ b/docs/old_readme.txt @@ -47,7 +47,7 @@ Note that not all programs that satisfy the following are valid; for example, th defs = ... def = <0 or more decorators> def (: , : ...): OR <0 or more decorators> def (: , : ...) -> : - decorator = @constant OR @payable OR @internal + decorator = @constant OR @payable OR @private OR @public argname = body = ... stmt = = @@ -151,14 +151,14 @@ deadline: timestamp goal: wei_value refundIndex: num timelimit: timedelta - + # Setup global variables def __init__(_beneficiary: address, _goal: wei_value, _timelimit: timedelta): self.beneficiary = _beneficiary self.deadline = block.timestamp + _timelimit self.timelimit = _timelimit self.goal = _goal - + # Participate in this crowdfunding campaign @payable def participate(): @@ -166,12 +166,12 @@ def participate(): nfi = self.nextFunderIndex self.funders[nfi] = {sender: msg.sender, value: msg.value} self.nextFunderIndex = nfi + 1 - + # Enough money was raised! Send funds to the beneficiary def finalize(): assert block.timestamp >= self.deadline and self.balance >= self.goal selfdestruct(self.beneficiary) - + # Not enough money was raised! Refund everyone (max 30 people at a time # to avoid gas limit issues) def refund(): @@ -187,10 +187,10 @@ def refund(): ``` -# Installation -Don't panic if installation fails, Viper is still under development and constant changes. Installation will be much simplified/optimized after a stable version release. +# Installation +Don't panic if installation fails, Viper is still under development and constant changes. Installation will be much simplified/optimized after a stable version release. -Take a deep breath and follow, please create an issue if any errors encountered. +Take a deep breath and follow, please create an issue if any errors encountered. It is **strongly recommended** to install in **a virtual Python environment (normally either `virtualenv` or `venv`)**, so that new packages installed and dependencies built are strictly contained in your viper project and will not alter/affect your other dev environment set-up. @@ -200,7 +200,7 @@ To find out how to set-up virtual environment, check out: [virtualenv guide](htt - **Ubuntu (16.04 LTS)** 1. Package update: ``` -sudo apt-get update +sudo apt-get update sudo apt-get -y upgrade ``` @@ -229,7 +229,7 @@ source viper/bin/activate ``` To deactivate and return to default environment, (you should see the "(viper)" at the front disappeared.) ``` -deactivate +deactivate ``` *Alternatively*: It is handy to use `pyenv` [https://github.com/pyenv/pyenv] to help manage your Python 3.6.2 or higher when Python releases new version. It works like a python version manager. @@ -240,7 +240,7 @@ pyenv virtualenv viper 5. Now, we are talking business, clone this Viper repo and install and test, and Walla! ``` git clone https://github.com/ethereum/viper.git -cd viper +cd viper python setup.py install python setup.py test ``` @@ -251,10 +251,10 @@ python setup.py test 2. Make sure your python is 3.6 or higher. If not, you could checkout [python3 for MacOS guide](http://python-guide.readthedocs.io/en/latest/starting/install3/osx/) -3. Now, go ahead and clone viper repo (not within `pyethereum` folder), and you run install and test, and Walla !! +3. Now, go ahead and clone viper repo (not within `pyethereum` folder), and you run install and test, and Walla !! ``` git clone https://github.com/ethereum/viper.git -cd viper +cd viper python setup.py install python setup.py test ``` @@ -264,7 +264,7 @@ If it fails with some error message on `openssl`, do the following: env LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/include" pip install scrypt ``` -# Compile +# Compile To compile your file, use: ``` viper yourFileName.v.py diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index 24a8d7ccb7..4eebc78d47 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -41,7 +41,7 @@ Functions are the executable units of code within a contract. :ref:`function-calls` can happen internally or externally and have different levels of visibility (:ref:`visibility-and-getters`) -towards other contracts. Functions must be decorated with either @public or @internal. +towards other contracts. Functions must be decorated with either @public or @private. .. _structure-events: diff --git a/tests/parser/features/decorators/test_internal.py b/tests/parser/features/decorators/test_private.py similarity index 64% rename from tests/parser/features/decorators/test_internal.py rename to tests/parser/features/decorators/test_private.py index 8fa0d39dcb..b7c05f30bf 100644 --- a/tests/parser/features/decorators/test_internal.py +++ b/tests/parser/features/decorators/test_private.py @@ -3,9 +3,9 @@ get_contract_with_gas_estimation, get_contract -def test_internal_test(): - internal_test = """ -@internal +def test_private_test(): + private_test_code = """ +@private def a() -> num: return 5 @@ -14,7 +14,7 @@ def returnten() -> num: return self.a() * 2 """ - c = get_contract_with_gas_estimation(internal_test) + c = get_contract_with_gas_estimation(private_test_code) assert c.returnten() == 10 - print("Passed internal function test") + print("Passed private function test") diff --git a/tests/parser/features/decorators/test_public.py b/tests/parser/features/decorators/test_public.py index 9739d2c0cb..0037a4d658 100644 --- a/tests/parser/features/decorators/test_public.py +++ b/tests/parser/features/decorators/test_public.py @@ -8,7 +8,7 @@ def test_invalid_if_both_public_and_internal(assert_compile_failed): code = """ @public -@internal +@private def foo(): x = 1 """ @@ -23,6 +23,3 @@ def foo(): """ assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) - - - diff --git a/tests/parser/syntax/test_invalids.py b/tests/parser/syntax/test_invalids.py index 945d30da7b..3e794b2250 100644 --- a/tests/parser/syntax/test_invalids.py +++ b/tests/parser/syntax/test_invalids.py @@ -160,7 +160,7 @@ def foo() -> num: must_succeed(""" x: num -@internal +@private def foo() -> num: self.x = 5 """) diff --git a/viper/function_signature.py b/viper/function_signature.py index 225af7dd88..aa6af9535b 100644 --- a/viper/function_signature.py +++ b/viper/function_signature.py @@ -34,13 +34,13 @@ def size(self): # Function signature object class FunctionSignature(): - def __init__(self, name, args, output_type, const, payable, internal, sig, method_id): + def __init__(self, name, args, output_type, const, payable, private, sig, method_id): self.name = name self.args = args self.output_type = output_type self.const = const self.payable = payable - self.internal = internal + self.private = private self.sig = sig self.method_id = method_id self.gas = None @@ -70,22 +70,22 @@ def from_definition(cls, code): pos += get_size_of_type(parsed_type) * 32 # Apply decorators - const, payable, internal, public = False, False, False, False + const, payable, private, public = False, False, False, False for dec in code.decorator_list: if isinstance(dec, ast.Name) and dec.id == "constant": const = True elif isinstance(dec, ast.Name) and dec.id == "payable": payable = True - elif isinstance(dec, ast.Name) and dec.id == "internal": - internal = True + elif isinstance(dec, ast.Name) and dec.id == "private": + private = True elif isinstance(dec, ast.Name) and dec.id == "public": public = True else: raise StructureException("Bad decorator", dec) - if public and internal: - raise StructureException("Cannot use public and internal decorators on the same function") - if not public and not internal and not isinstance(code.body[0], ast.Pass): - raise StructureException("Function visibility must be declared (@public or @internal)", code) + if public and private: + raise StructureException("Cannot use public and private decorators on the same function", code) + if not public and not private and not isinstance(code.body[0], ast.Pass): + raise StructureException("Function visibility must be declared (@public or @private)", code) # Determine the return type and whether or not it's constant. Expects something # of the form: # def foo(): ... @@ -105,7 +105,7 @@ def from_definition(cls, code): sig = name + '(' + ','.join([canonicalize_type(parse_type(arg.annotation, None)) for arg in code.args.args]) + ')' # Take the first 4 bytes of the hash of the sig to get the method ID method_id = fourbytes_to_int(sha3(bytes(sig, 'utf-8'))[:4]) - return cls(name, args, output_type, const, payable, internal, sig, method_id) + return cls(name, args, output_type, const, payable, private, sig, method_id) def _generate_output_abi(self): t = self.output_type diff --git a/viper/parser/parser.py b/viper/parser/parser.py index d5f51d15f8..cc85f8045d 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -313,7 +313,7 @@ def mk_full_signature(code): o.append(sig.to_abi_dict()) for code in _defs: sig = FunctionSignature.from_definition(code) - if not sig.internal: + if not sig.private: o.append(sig.to_abi_dict()) return o @@ -445,7 +445,7 @@ def parse_func(code, _globals, sigs, origcode, _vars=None): # Add asserts for payable and internal if not sig.payable: clampers.append(['assert', ['iszero', 'callvalue']]) - if sig.internal: + if sig.private: clampers.append(['assert', ['eq', 'caller', 'address']]) # Fill in variable positions for arg in sig.args: From f947b7b6143ccb4f9802ba149e3fa6ea68f5d6ea Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Thu, 30 Nov 2017 11:50:39 +0200 Subject: [PATCH 105/162] Add support for logging a variable of list type. --- viper/parser/parser.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index d5f51d15f8..7bffbdd437 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -698,9 +698,20 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): elif isinstance(typ, ListType): maxlen += (typ.count - 1) * 32 typ = typ.subtype - holder, maxlen = pack_args_by_32(holder, maxlen, arg.elts[0], typ, context, placeholder) - for j, arg2 in enumerate(arg.elts[1:]): - holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32))) + + if isinstance(arg, ast.Name): + size = context.vars[arg.id].size + pos = context.vars[arg.id].pos + for i in range(0, size): + offset = 32 * i + holder.append( + LLLnode.from_list(['mstore', placeholder + offset, ['mload', pos + offset]], typ=typ, location='memory') + ) + else: # is list literal. + holder, maxlen = pack_args_by_32(holder, maxlen, arg.elts[0], typ, context, placeholder) + for j, arg2 in enumerate(arg.elts[1:]): + holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32))) + return holder, maxlen From 21ebf91df200b83f9bd3d46aea8ac8232aa2e149 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Thu, 30 Nov 2017 11:50:56 +0200 Subject: [PATCH 106/162] Adds tests for logging of list types. --- tests/parser/features/test_logging.py | 31 ++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index c3394dbaee..f897299e09 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -1,6 +1,6 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract, assert_tx_failed + get_contract_with_gas_estimation, get_contract, assert_tx_failed, get_last_log from viper.exceptions import VariableDeclarationException, TypeMismatchException, StructureException @@ -448,3 +448,32 @@ def ioo(inp: bytes <= 100): c.ioo(b"moo4") assert s.head_state.receipts[-1].logs[0].data == b'moo4' print("Passed raw log tests") + + +def test_variable_list_packing(get_last_log): + code = """ +Bar: __log__({_value: num[4]}) + +@public +def foo(): + a = [1, 2, 3, 4] + log.Bar(a) + """ + c = get_contract_with_gas_estimation(code) + + c.foo() + assert get_last_log(t, c)["_value"] == [1, 2, 3, 4] + + +def test_literal_list_packing(get_last_log): + code = """ +Bar: __log__({_value: num[4]}) + +@public +def foo(): + log.Bar([1, 2, 3, 4]) + """ + c = get_contract_with_gas_estimation(code) + + c.foo() + assert get_last_log(t, c)["_value"] == [1, 2, 3, 4] From c9d6d35b2acd885b94a1f1a668f4482a60afad60 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Thu, 30 Nov 2017 12:43:17 +0200 Subject: [PATCH 107/162] Fix for log packing on list variable. --- viper/parser/parser.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 7bffbdd437..69a0d870f1 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -704,9 +704,8 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): pos = context.vars[arg.id].pos for i in range(0, size): offset = 32 * i - holder.append( - LLLnode.from_list(['mstore', placeholder + offset, ['mload', pos + offset]], typ=typ, location='memory') - ) + arg2 = LLLnode.from_list(pos + offset, typ=typ, location='memory') + holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32))) else: # is list literal. holder, maxlen = pack_args_by_32(holder, maxlen, arg.elts[0], typ, context, placeholder) for j, arg2 in enumerate(arg.elts[1:]): From a779a3c44dd12b74d5800e19de70b035310f8b59 Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 28 Nov 2017 19:47:22 -0700 Subject: [PATCH 108/162] Add error if log has wrong number of arguments --- viper/parser/stmt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index 096a409a91..324616e7ba 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -135,6 +135,8 @@ def call(self): if self.stmt.func.attr not in self.context.sigs['self']: raise VariableDeclarationException("Event not declared yet: %s" % self.stmt.func.attr) event = self.context.sigs['self'][self.stmt.func.attr] + if len(event.indexed_list) != len(self.stmt.args): + raise VariableDeclarationException("%s received %s arguments but expected %s" % (event.name, len(self.stmt.args), len(event.indexed_list))) topics_types, topics = [], [] data_types, data = [], [] for pos, is_indexed in enumerate(event.indexed_list): From a0a4358d693522308c990df13a479b13b0ee5aee Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 28 Nov 2017 19:47:51 -0700 Subject: [PATCH 109/162] Add log test and improve bytes as input --- tests/parser/features/test_logging.py | 83 +++++++++++++++++++++++++-- viper/parser/parser.py | 2 +- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index c3394dbaee..e462e9ceaa 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -1,6 +1,6 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract, assert_tx_failed + get_contract_with_gas_estimation, get_contract from viper.exceptions import VariableDeclarationException, TypeMismatchException, StructureException @@ -107,7 +107,11 @@ def test_event_logging_cannot_have_more_than_three_topics(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3), arg2: indexed(bytes <= 4), arg3: indexed(address), arg4: indexed(num)}) +Transfer: __log__({_from: indexed(num), _to: indexed(num), _value: num}) + @public +def foo(): + log.Transfer(1, 2, 3, 4, 5, 6, 7, 8, 9 ,10)@public def foo(): log.MyLog('bar', 'home', self) """ @@ -163,7 +167,7 @@ def foo(): assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': [1, 2], 'arg2': [timestamp, timestamp + 1, timestamp + 2], 'arg3': [[1, 2], [1, 2]], '_event_type': b'MyLog'} -def test_logging_with_input_bytes(bytes_helper): +def test_logging_with_input_bytes_1(bytes_helper): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 4), arg2: indexed(bytes <= 29), arg3: bytes<=31}) @@ -171,6 +175,7 @@ def test_logging_with_input_bytes(bytes_helper): def foo(arg1: bytes <= 29, arg2: bytes <= 31): log.MyLog('bar', arg1, arg2) """ + c = get_contract_with_gas_estimation(loggy_code) c.foo('bar', 'foo') logs = s.head_state.receipts[-1].logs[-1] @@ -182,7 +187,53 @@ def foo(arg1: bytes <= 29, arg2: bytes <= 31): # # Event abi is created correctly assert c.translator.event_data[event_id] == {'types': ['bytes4', 'bytes29', 'bytes31'], 'name': 'MyLog', 'names': ['arg1', 'arg2', 'arg3'], 'indexed': [True, True, False], 'anonymous': False} # Event is decoded correctly - assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': b'bar\x00', 'arg2': bytes_helper('bar', 29), 'arg3': bytes_helper('foo', 31), '_event_type': b'MyLog'} + assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': b'bar\x00', 'arg2': bytes_helper('bar', 29), 'arg3': bytes_helper('', 28) + b'foo', '_event_type': b'MyLog'} + + +def test_event_logging_with_bytes_input_2(t, bytes_helper): + loggy_code = """ +MyLog: __log__({arg1: bytes <= 20}) + +@public +def foo(_arg1: bytes <= 20): + log.MyLog(_arg1) + """ + + c = get_contract(loggy_code) + c.foo('hello') + logs = s.head_state.receipts[-1].logs[-1] + event_id = u.bytes_to_int(u.sha3(bytes('MyLog(bytes20)', 'utf-8'))) + # Event id is always the first topic + assert logs.topics[0] == event_id + # Event id is calculated correctly + assert c.translator.event_data[event_id] + # Event abi is created correctly + assert c.translator.event_data[event_id] == {'types': ['bytes20'], 'name': 'MyLog', 'names': ['arg1'], 'indexed': [False], 'anonymous': False} + # Event is decoded correctly + assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': bytes_helper('', 15) + b'hello', '_event_type': b'MyLog'} + + +def test_event_logging_with_bytes_input_3(bytes_helper): + loggy_code = """ +MyLog: __log__({arg1: bytes <= 5}) + +@public +def foo(_arg1: bytes <= 5): + log.MyLog(_arg1) + """ + + c = get_contract(loggy_code) + c.foo('hello') + logs = s.head_state.receipts[-1].logs[-1] + event_id = u.bytes_to_int(u.sha3(bytes('MyLog(bytes5)', 'utf-8'))) + # Event id is always the first topic + assert logs.topics[0] == event_id + # Event id is calculated correctly + assert c.translator.event_data[event_id] + # Event abi is created correctly + assert c.translator.event_data[event_id] == {'types': ['bytes5'], 'name': 'MyLog', 'names': ['arg1'], 'indexed': [False], 'anonymous': False} + # Event is decoded correctly + assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': b'hello', '_event_type': b'MyLog'} def test_event_logging_with_data_with_different_types(): @@ -208,7 +259,7 @@ def foo(): assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': 123, 'arg2': b'home', 'arg3': b'bar', 'arg4': '0xc305c901078781c232a2a521c2af7980f8385ee9', 'arg5': '0x' + c.address.hex(), 'arg6': s.head_state.timestamp, '_event_type': b'MyLog'} -def test_event_logging_with_topics_and_data(): +def test_event_logging_with_topics_and_data_1(): loggy_code = """ MyLog: __log__({arg1: indexed(num), arg2: bytes <= 3}) @@ -404,17 +455,37 @@ def test_logging_fails_after_a_global_declaration(assert_tx_failed): def test_logging_fails_after_a_function_declaration(assert_tx_failed): loggy_code = """ - @public def foo(): pass MyLog: __log__({arg1: bytes <= 3}) """ - t.s = s assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), StructureException) +def test_logging_fails_when_number_of_arguments_is_greater_than_declaration(assert_tx_failed): + loggy_code = """ +MyLog: __log__({arg1: num}) + +@public +def foo(): + log.MyLog(1, 2) +""" + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) + + +def test_logging_fails_when_number_of_arguments_is_less_than_declaration(assert_tx_failed): + loggy_code = """ +MyLog: __log__({arg1: num, arg2: num}) + +@public +def foo(): + log.MyLog(1) +""" + assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) + + def test_loggy_code(): loggy_code = """ s: bytes <= 100 diff --git a/viper/parser/parser.py b/viper/parser/parser.py index cc85f8045d..375c65e992 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -693,7 +693,7 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): if input.typ.maxlen > typ.maxlen: raise TypeMismatchException("Data input bytes are to big: %r %r" % (input.typ, typ)) if arg.id in context.vars: - size = context.vars[arg.id].size + size = input.typ.maxlen holder.append(LLLnode.from_list(['mstore', placeholder, byte_array_to_num(parse_expr(arg, context), arg, 'num256', size)], typ=typ, location='memory')) elif isinstance(typ, ListType): maxlen += (typ.count - 1) * 32 From 35fb4de3c9a8141cfdf07f7bed11b5f8de4bc526 Mon Sep 17 00:00:00 2001 From: David Knott Date: Thu, 30 Nov 2017 15:40:17 -0700 Subject: [PATCH 110/162] Improve variable naming / fix bytes as input --- viper/parser/parser.py | 40 ++++++++++++++++++++-------------------- viper/parser/stmt.py | 12 ++++++------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 375c65e992..c5aa28571a 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -645,14 +645,15 @@ def parse_stmt(stmt, context): return Stmt(stmt, context).lll_node -def pack_logging_topics(event_id, args, topics_types, context): +def pack_logging_topics(event_id, args, expected_topics, context): topics = [event_id] - for pos, typ in enumerate(topics_types): + for pos, expected_topic in enumerate(expected_topics): + typ = expected_topic.typ arg = args[pos] - input = parse_expr(arg, context) + value = parse_expr(arg, context) if isinstance(typ, ByteArrayType) and (isinstance(arg, ast.Str) or (isinstance(arg, ast.Name) and arg.id not in reserved_words)): - if input.typ.maxlen > typ.maxlen: - raise TypeMismatchException("Topic input bytes are to big: %r %r" % (input.typ, typ)) + if value.typ.maxlen > typ.maxlen: + raise TypeMismatchException("Topic input bytes are to big: %r %r" % (value.typ, typ)) if isinstance(arg, ast.Str): bytez, bytez_length = string_to_bytes(arg.s) if len(bytez) > 32: @@ -660,19 +661,19 @@ def pack_logging_topics(event_id, args, topics_types, context): topics.append(bytes_to_int(bytez + b'\x00' * (32 - bytez_length))) else: size = context.vars[arg.id].size - topics.append(byte_array_to_num(input, arg, 'num256', size)) + topics.append(byte_array_to_num(value, arg, 'num256', size)) else: - input = unwrap_location(input) - input = base_type_conversion(input, input.typ, typ, arg) - topics.append(input) + value = unwrap_location(value) + value = base_type_conversion(value, value.typ, typ) + topics.append(value) return topics def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): if isinstance(typ, BaseType): - input = parse_expr(arg, context) - input = base_type_conversion(input, input.typ, typ, arg) - holder.append(LLLnode.from_list(['mstore', placeholder, input], typ=typ, location='memory')) + value = parse_expr(arg, context) + value = base_type_conversion(value, value.typ, typ) + holder.append(LLLnode.from_list(['mstore', placeholder, value], typ=typ, location='memory')) elif isinstance(typ, ByteArrayType): bytez = b'' # String literals @@ -689,12 +690,10 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): holder.append(LLLnode.from_list(['mstore', placeholder, bytes_to_int(bytez + b'\x00' * (32 - bytez_length))], typ=typ, location='memory')) # Variables else: - input = parse_expr(arg, context) - if input.typ.maxlen > typ.maxlen: - raise TypeMismatchException("Data input bytes are to big: %r %r" % (input.typ, typ)) - if arg.id in context.vars: - size = input.typ.maxlen - holder.append(LLLnode.from_list(['mstore', placeholder, byte_array_to_num(parse_expr(arg, context), arg, 'num256', size)], typ=typ, location='memory')) + value = parse_expr(arg, context) + if value.typ.maxlen > typ.maxlen: + raise TypeMismatchException("Data input bytes are to big: %r %r" % (value.typ, typ)) + holder.append(LLLnode.from_list(['mstore', placeholder, ['mload', ['add', value, 32]]], typ=typ, location='memory')) elif isinstance(typ, ListType): maxlen += (typ.count - 1) * 32 typ = typ.subtype @@ -705,13 +704,14 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): # Pack logging data arguments -def pack_logging_data(types, args, context): +def pack_logging_data(expected_data, args, context): # Checks to see if there's any data if not args: return ['seq'], 0, 0 holder = ['seq'] maxlen = len(args) * 32 - for i, (arg, typ) in enumerate(zip(args, types)): + for i, (arg, data) in enumerate(zip(args, expected_data)): + typ = data.typ holder, maxlen = pack_args_by_32(holder, maxlen, arg, typ, context, context.new_placeholder(BaseType(32))) return holder, maxlen, holder[1].to_list()[1][0] diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index 324616e7ba..0f772c1406 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -137,17 +137,17 @@ def call(self): event = self.context.sigs['self'][self.stmt.func.attr] if len(event.indexed_list) != len(self.stmt.args): raise VariableDeclarationException("%s received %s arguments but expected %s" % (event.name, len(self.stmt.args), len(event.indexed_list))) - topics_types, topics = [], [] - data_types, data = [], [] + expected_topics, topics = [], [] + expected_data, data = [], [] for pos, is_indexed in enumerate(event.indexed_list): if is_indexed: - topics_types.append(event.args[pos].typ) + expected_topics.append(event.args[pos]) topics.append(self.stmt.args[pos]) else: - data_types.append(event.args[pos].typ) + expected_data.append(event.args[pos]) data.append(self.stmt.args[pos]) - topics = pack_logging_topics(event.event_id, topics, topics_types, self.context) - inargs, inargsize, inarg_start = pack_logging_data(data_types, data, self.context) + topics = pack_logging_topics(event.event_id, topics, expected_topics, self.context) + inargs, inargsize, inarg_start = pack_logging_data(expected_data, data, self.context) return LLLnode.from_list(['seq', inargs, ["log" + str(len(topics)), inarg_start, inargsize] + topics], typ=None, pos=getpos(self.stmt)) else: raise StructureException("Unsupported operator: %r" % ast.dump(self.stmt), self.stmt) From a36509abc562ba6623583678a9da82d7cc419b17 Mon Sep 17 00:00:00 2001 From: David Knott Date: Thu, 30 Nov 2017 15:40:23 -0700 Subject: [PATCH 111/162] Clean up tests --- tests/parser/features/test_logging.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index e462e9ceaa..c768694259 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -106,14 +106,6 @@ def bar(): def test_event_logging_cannot_have_more_than_three_topics(assert_tx_failed): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3), arg2: indexed(bytes <= 4), arg3: indexed(address), arg4: indexed(num)}) - -Transfer: __log__({_from: indexed(num), _to: indexed(num), _value: num}) - -@public -def foo(): - log.Transfer(1, 2, 3, 4, 5, 6, 7, 8, 9 ,10)@public -def foo(): - log.MyLog('bar', 'home', self) """ assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) @@ -187,7 +179,7 @@ def foo(arg1: bytes <= 29, arg2: bytes <= 31): # # Event abi is created correctly assert c.translator.event_data[event_id] == {'types': ['bytes4', 'bytes29', 'bytes31'], 'name': 'MyLog', 'names': ['arg1', 'arg2', 'arg3'], 'indexed': [True, True, False], 'anonymous': False} # Event is decoded correctly - assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': b'bar\x00', 'arg2': bytes_helper('bar', 29), 'arg3': bytes_helper('', 28) + b'foo', '_event_type': b'MyLog'} + assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': b'bar\x00', 'arg2': bytes_helper('bar', 29), 'arg3': bytes_helper('foo', 31), '_event_type': b'MyLog'} def test_event_logging_with_bytes_input_2(t, bytes_helper): @@ -199,7 +191,7 @@ def foo(_arg1: bytes <= 20): log.MyLog(_arg1) """ - c = get_contract(loggy_code) + c = get_contract_with_gas_estimation(loggy_code) c.foo('hello') logs = s.head_state.receipts[-1].logs[-1] event_id = u.bytes_to_int(u.sha3(bytes('MyLog(bytes20)', 'utf-8'))) @@ -210,7 +202,7 @@ def foo(_arg1: bytes <= 20): # Event abi is created correctly assert c.translator.event_data[event_id] == {'types': ['bytes20'], 'name': 'MyLog', 'names': ['arg1'], 'indexed': [False], 'anonymous': False} # Event is decoded correctly - assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': bytes_helper('', 15) + b'hello', '_event_type': b'MyLog'} + assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': bytes_helper('hello', 20), '_event_type': b'MyLog'} def test_event_logging_with_bytes_input_3(bytes_helper): From dd9863ab2fce93874ad2842886000f3b5ea5289b Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Fri, 1 Dec 2017 12:24:10 +0200 Subject: [PATCH 112/162] Adds Fix #531. Check return type of lists match. --- tests/parser/syntax/test_list.py | 14 ++++++++++++++ viper/parser/stmt.py | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/parser/syntax/test_list.py b/tests/parser/syntax/test_list.py index f4243bb524..a13547fcfb 100644 --- a/tests/parser/syntax/test_list.py +++ b/tests/parser/syntax/test_list.py @@ -109,6 +109,20 @@ def foo(): def foo(): x = self.b[0].cow """, + """ +@public +def foo()->bool[2]: + a: decimal[2] + a[0] = 1 + return a + """, + """ +@public +def foo()->bool[2]: + a: bool[1000] + a[0] = 1 + return a + """ ] diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index 096a409a91..df0ec6b66d 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -1,4 +1,5 @@ import ast +import re from viper.exceptions import ( ConstancyViolationException, @@ -350,6 +351,15 @@ def parse_return(self): raise Exception("Invalid location: %s" % sub.location) elif isinstance(sub.typ, ListType): + sub_base_type = re.split(r'\(|\[', str(sub.typ.subtype))[0] + ret_base_type = re.split(r'\(|\[', str(self.context.return_type.subtype))[0] + if sub_base_type != ret_base_type and sub.value != 'multi': + raise TypeMismatchException( + "List return type %r does not match specified return type, expecting %r" % ( + sub_base_type, ret_base_type + ), + self.stmt + ) if sub.location == "memory" and sub.value != "multi": return LLLnode.from_list(['return', sub, get_size_of_type(self.context.return_type) * 32], typ=None, pos=getpos(self.stmt)) From 729c2ebcce8bad5198ff2b9952d67f5bdbdf2c96 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Fri, 1 Dec 2017 14:59:26 +0200 Subject: [PATCH 113/162] Adds more tests for logging of lists. --- tests/parser/features/test_logging.py | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index f897299e09..34bb4a5a77 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -477,3 +477,31 @@ def foo(): c.foo() assert get_last_log(t, c)["_value"] == [1, 2, 3, 4] + + +def test_passed_list_packing(get_last_log): + code = """ +Bar: __log__({_value: num[4]}) + +@public +def foo(barbaric: num[4]): + log.Bar(barbaric) + """ + c = get_contract_with_gas_estimation(code) + + c.foo([4, 5, 6, 7]) + assert get_last_log(t, c)["_value"] == [4, 5, 6, 7] + + +def test_variable_decimal_list_packing(get_last_log): + code = """ +Bar: __log__({_value: decimal[4]}) + +@public +def foo(): + log.Bar([1.11, 2.22, 3.33, 4.44]) + """ + c = get_contract_with_gas_estimation(code) + + c.foo() + assert get_last_log(t, c)["_value"] == [1.11, 2.22, 3.33, 4.44] From 10b19ec01a669c92132e6eb06389ecd9012739d8 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Fri, 1 Dec 2017 17:00:12 +0200 Subject: [PATCH 114/162] Adds Fix #504 for clamping decimal literals correctly. --- tests/parser/exceptions/test_invalid_literal_exception.py | 8 ++++++++ viper/parser/expr.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/parser/exceptions/test_invalid_literal_exception.py b/tests/parser/exceptions/test_invalid_literal_exception.py index c38995b88b..67435f4394 100644 --- a/tests/parser/exceptions/test_invalid_literal_exception.py +++ b/tests/parser/exceptions/test_invalid_literal_exception.py @@ -91,6 +91,14 @@ def foo(): @public def foo(): x = as_num256(3.1415) + """, + """ +# Test decimal limit. +a:decimal + +@public +def foo(): + self.a = 170141183460469231731687303715884105727.888 """ ] diff --git a/viper/parser/expr.py b/viper/parser/expr.py index b690397e34..9b5a17f10d 100644 --- a/viper/parser/expr.py +++ b/viper/parser/expr.py @@ -84,7 +84,7 @@ def number(self): return LLLnode.from_list(self.expr.n, typ=BaseType('num', None), pos=getpos(self.expr)) elif isinstance(self.expr.n, float): numstring, num, den = get_number_as_fraction(self.expr, self.context) - if not (-2**127 * den < num < 2**127 * den): + if not (SizeLimits.MINNUM * den < num < SizeLimits.MAXNUM * den): raise InvalidLiteralException("Number out of range: " + numstring, self.expr) if DECIMAL_DIVISOR % den: raise InvalidLiteralException("Too many decimal places: " + numstring, self.expr) From 8025a6a5cb4ef245e12242c5184dfdaa59e97910 Mon Sep 17 00:00:00 2001 From: David Knott Date: Sat, 2 Dec 2017 17:45:03 -0700 Subject: [PATCH 115/162] Use revert for clamps --- viper/compile_lll.py | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/viper/compile_lll.py b/viper/compile_lll.py index b4044a3875..11e867cde2 100644 --- a/viper/compile_lll.py +++ b/viper/compile_lll.py @@ -27,6 +27,15 @@ def is_symbol(i): return isinstance(i, str) and i[:5] == '_sym_' +def get_revert(): + o = [] + end_symbol = mksymbol() + o.extend([end_symbol, 'JUMPI']) + o.extend(['PUSH1', 0, 'DUP1', 'REVERT']) + o.extend([end_symbol, 'JUMPDEST']) + return o + + # Compiles LLL to assembly def compile_to_assembly(code, withargs=None, break_dest=None, height=0): if withargs is None: @@ -155,10 +164,7 @@ def compile_to_assembly(code, withargs=None, break_dest=None, height=0): # Assert (if false, exit) elif code.value == 'assert': o = compile_to_assembly(code.args[0], withargs, break_dest, height) - end_symbol = mksymbol() - o.extend([end_symbol, 'JUMPI']) - o.extend(['PUSH1', 0, 'DUP1', 'REVERT']) - o.extend([end_symbol, 'JUMPDEST']) + o.extend(get_revert()) return o # Unsigned/signed clamp, check less-than elif code.value in ('uclamplt', 'uclample', 'clamplt', 'clample', 'uclampgt', 'uclampge', 'clampgt', 'clampge'): @@ -176,22 +182,22 @@ def compile_to_assembly(code, withargs=None, break_dest=None, height=0): o.extend(['DUP2']) # Stack: num num bound if code.value == 'uclamplt': - o.extend(["LT", 'ISZERO']) + o.extend(['LT']) elif code.value == "clamplt": - o.extend(["SLT", 'ISZERO']) + o.extend(['SLT']) elif code.value == "uclample": - o.extend(["GT"]) + o.extend(['GT', 'ISZERO']) elif code.value == "clample": - o.extend(["SGT"]) + o.extend(['SGT', 'ISZERO']) elif code.value == 'uclampgt': - o.extend(["GT", 'ISZERO']) + o.extend(['GT']) elif code.value == "clampgt": - o.extend(["SGT", 'ISZERO']) + o.extend(['SGT']) elif code.value == "uclampge": - o.extend(["LT"]) + o.extend(['LT', 'ISZERO']) elif code.value == "clampge": - o.extend(["SLT"]) - o.extend(['PC', 'JUMPI']) + o.extend(['SLT', 'ISZERO']) + o.extend(get_revert()) return o # Signed clamp, check against upper and lower bounds elif code.value in ('clamp', 'uclamp'): @@ -202,12 +208,14 @@ def compile_to_assembly(code, withargs=None, break_dest=None, height=0): o.extend(['DUP1']) o.extend(compile_to_assembly(code.args[2], withargs, break_dest, height + 3)) o.extend(['SWAP1', comp1, 'PC', 'JUMPI']) - o.extend(['DUP1', 'SWAP2', 'SWAP1', comp2, 'PC', 'JUMPI']) + o.extend(['DUP1', 'SWAP2', 'SWAP1', comp2, 'ISZERO']) + o.extend(get_revert()) return o # Checks that a value is nonzero elif code.value == 'clamp_nonzero': o = compile_to_assembly(code.args[0], withargs, break_dest, height) - o.extend(['DUP1', 'ISZERO', 'PC', 'JUMPI']) + o.extend(['DUP1']) + o.extend(get_revert()) return o # SHA3 a single value elif code.value == 'sha3_32': From af8f618163470a630218fd682e25bd2cb17a7058 Mon Sep 17 00:00:00 2001 From: David Knott Date: Fri, 1 Dec 2017 16:26:59 -0700 Subject: [PATCH 116/162] Fix exponents with units --- tests/parser/types/numbers/test_num.py | 32 ++++++++++++++++++++++++++ viper/parser/expr.py | 2 ++ 2 files changed, 34 insertions(+) diff --git a/tests/parser/types/numbers/test_num.py b/tests/parser/types/numbers/test_num.py index 9de8ed2d57..21d57e0677 100644 --- a/tests/parser/types/numbers/test_num.py +++ b/tests/parser/types/numbers/test_num.py @@ -1,6 +1,8 @@ import pytest from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ get_contract_with_gas_estimation, get_contract, assert_tx_failed +from viper.exceptions import TypeMismatchException + def test_exponents_with_nums(): exp_code = """ @@ -17,6 +19,7 @@ def _num_exp(x: num, y: num) -> num: assert c._num_exp(3,3) == 27 assert c._num_exp(72,19) == 72**19 + def test_negative_nums(assert_tx_failed): negative_nums_code = """ @public @@ -33,6 +36,23 @@ def _negative_exp() -> num: assert c._negative_num() == -1 assert c._negative_exp() == -3 + +def test_exponents_with_units(assert_compile_failed): + code = """ +@public +def foo() -> num(wei): + a: num(wei) + b: num + c: num(wei) + a = 2 + b = 2 + c = a ** b + return c +""" + c = get_contract_with_gas_estimation(code) + assert c.foo() == 4 + + def test_num_bound(assert_tx_failed): num_bound_code = """ @public @@ -78,3 +98,15 @@ def _num_min() -> num: assert_tx_failed(lambda: c._num_add3(NUM_MAX, 1, -1)) assert c._num_add3(NUM_MAX, -1, 1) == NUM_MAX + + +def test_invalid_unit_exponent(assert_compile_failed): + code = """ +@public +def foo(): + a: num(wei) + b: num(wei) + c = a ** b +""" + assert_compile_failed(lambda: get_contract_with_gas_estimation(code), TypeMismatchException) + diff --git a/viper/parser/expr.py b/viper/parser/expr.py index b690397e34..8eb211524a 100644 --- a/viper/parser/expr.py +++ b/viper/parser/expr.py @@ -285,6 +285,8 @@ def arithmetic(self): elif isinstance(self.expr.op, ast.Pow): if left.typ.positional or right.typ.positional: raise TypeMismatchException("Cannot use positional values as exponential arguments!", self.expr) + if right.typ.unit: + raise TypeMismatchException("Cannot use unit values as exponents", self.expr) new_unit = combine_units(left.typ.unit, right.typ.unit) if ltyp == rtyp == 'num': o = LLLnode.from_list(['exp', left, right], typ=BaseType('num', new_unit), pos=getpos(self.expr)) From efd5f2d3cc550baf8940c2a752cdf35d799fc517 Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 28 Nov 2017 13:38:31 -0700 Subject: [PATCH 117/162] Create a contract data type --- viper/function_signature.py | 8 ++++---- viper/parser/expr.py | 8 +++++++- viper/parser/parser.py | 13 +++++++++---- viper/parser/stmt.py | 9 ++++++++- viper/types.py | 14 +++++++++++--- 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/viper/function_signature.py b/viper/function_signature.py index aa6af9535b..f5801be837 100644 --- a/viper/function_signature.py +++ b/viper/function_signature.py @@ -47,7 +47,7 @@ def __init__(self, name, args, output_type, const, payable, private, sig, method # Get a signature from a function definition @classmethod - def from_definition(cls, code): + def from_definition(cls, code, _sigs=None): name = code.name pos = 0 # Determine the arguments, expects something of the form def foo(arg1: num, arg2: num ... @@ -62,7 +62,7 @@ def from_definition(cls, code): raise VariableDeclarationException("Argument name invalid or reserved: " + arg.arg, arg) if arg.arg in (x.name for x in args): raise VariableDeclarationException("Duplicate function argument name: " + arg.arg, arg) - parsed_type = parse_type(typ, None) + parsed_type = parse_type(typ, None, _sigs) args.append(VariableRecord(arg.arg, pos, parsed_type, False)) if isinstance(parsed_type, ByteArrayType): pos += 32 @@ -95,14 +95,14 @@ def from_definition(cls, code): if not code.returns: output_type = None elif isinstance(code.returns, (ast.Name, ast.Compare, ast.Subscript, ast.Call, ast.Tuple)): - output_type = parse_type(code.returns, None) + output_type = parse_type(code.returns, None, _sigs) else: raise InvalidTypeException("Output type invalid or unsupported: %r" % parse_type(code.returns, None), code.returns) # Output type must be canonicalizable if output_type is not None: assert isinstance(output_type, TupleType) or canonicalize_type(output_type) # Get the canonical function signature - sig = name + '(' + ','.join([canonicalize_type(parse_type(arg.annotation, None)) for arg in code.args.args]) + ')' + sig = name + '(' + ','.join([canonicalize_type(parse_type(arg.annotation, None, _sigs)) for arg in code.args.args]) + ')' # Take the first 4 bytes of the hash of the sig to get the method ID method_id = fourbytes_to_int(sha3(bytes(sig, 'utf-8'))[:4]) return cls(name, args, output_type, const, payable, private, sig, method_id) diff --git a/viper/parser/expr.py b/viper/parser/expr.py index b690397e34..c09df0317b 100644 --- a/viper/parser/expr.py +++ b/viper/parser/expr.py @@ -463,7 +463,13 @@ def call(self): return external_contract_call_expr(self.expr, self.context, contract_name, contract_address) elif isinstance(self.expr.func.value, ast.Attribute) and self.expr.func.value.attr in self.context.sigs: contract_name = self.expr.func.value.attr - contract_address = Expr.parse_value_expr(ast.parse('self.token_address').body[0].value, self.context) + var = self.context.globals[self.expr.func.value.attr] + contract_address = unwrap_location(LLLnode.from_list(var.pos, typ=var.typ, location='storage', pos=getpos(self.expr), annotation='self.' + self.expr.func.value.attr)) + return external_contract_call_expr(self.expr, self.context, contract_name, contract_address) + elif isinstance(self.expr.func.value, ast.Attribute) and self.expr.func.value.attr in self.context.globals: + contract_name = self.context.globals[self.expr.func.value.attr].typ.unit + var = self.context.globals[self.expr.func.value.attr] + contract_address = unwrap_location(LLLnode.from_list(var.pos, typ=var.typ, location='storage', pos=getpos(self.expr), annotation='self.' + self.expr.func.value.attr)) return external_contract_call_expr(self.expr, self.context, contract_name, contract_address) else: raise StructureException("Unsupported operator: %r" % ast.dump(self.expr), self.expr) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index c5aa28571a..01fc174b80 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -200,10 +200,15 @@ def add_globals_and_events(_contracts, _defs, _events, _getters, _globals, item) premade_contract = premade_contracts[item.annotation.args[0].id] _contracts[item.target.id] = add_contract(premade_contract.body) _globals[item.target.id] = VariableRecord(item.target.id, len(_globals), BaseType('address'), True) + elif isinstance(item, ast.AnnAssign) and isinstance(item.annotation, ast.Name) and item.annotation.id in _contracts: + _globals[item.target.id] = VariableRecord(item.target.id, len(_globals), BaseType('address', item.annotation.id), True) elif isinstance(item.annotation, ast.Call) and item.annotation.func.id == "public": if len(item.annotation.args) != 1: raise StructureException("Public expects one arg (the type)") - typ = parse_type(item.annotation.args[0], 'storage') + if isinstance(item.annotation.args[0], ast.Name) and item.annotation.args[0].id in _contracts: + typ = BaseType('address', item.annotation.args[0].id) + else: + typ = parse_type(item.annotation.args[0], 'storage') _globals[item.target.id] = VariableRecord(item.target.id, len(_globals), typ, True) # Adding getters here for getter in mk_getter(item.target.id, typ): @@ -312,7 +317,7 @@ def mk_full_signature(code): sig = EventSignature.from_declaration(code) o.append(sig.to_abi_dict()) for code in _defs: - sig = FunctionSignature.from_definition(code) + sig = FunctionSignature.from_definition(code, _contracts) if not sig.private: o.append(sig.to_abi_dict()) return o @@ -345,7 +350,7 @@ def parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, ori sub.append(parse_func(_def, _globals, {**{'self': sigs}, **external_contracts}, origcode)) sub[-1].total_gas += add_gas add_gas += 30 - sig = FunctionSignature.from_definition(_def) + sig = FunctionSignature.from_definition(_def, external_contracts) sig.gas = sub[-1].total_gas sigs[sig.name] = sig if runtime_only: @@ -424,7 +429,7 @@ def make_clamper(datapos, mempos, typ, is_init=False): def parse_func(code, _globals, sigs, origcode, _vars=None): if _vars is None: _vars = {} - sig = FunctionSignature.from_definition(code) + sig = FunctionSignature.from_definition(code, sigs) # Check for duplicate variables with globals for arg in sig.args: if arg.name in _globals: diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index 0f772c1406..aa9becef9f 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -129,7 +129,14 @@ def call(self): return external_contract_call_stmt(self.stmt, self.context, contract_name, contract_address) elif isinstance(self.stmt.func.value, ast.Attribute) and self.stmt.func.value.attr in self.context.sigs: contract_name = self.stmt.func.value.attr - contract_address = Expr.parse_value_expr(ast.parse('self.token_address').body[0].value, self.context) + var = self.context.globals[self.stmt.func.value.attr] + contract_address = unwrap_location(LLLnode.from_list(var.pos, typ=var.typ, location='storage', pos=getpos(self.stmt), annotation='self.' + self.stmt.func.value.attr)) + return external_contract_call_stmt(self.stmt, self.context, contract_name, contract_address) + elif isinstance(self.stmt.func.value, ast.Attribute) and self.stmt.func.value.attr in self.context.globals: + contract_name = self.context.globals[self.stmt.func.value.attr].typ.unit + var = self.context.globals[self.stmt.func.value.attr] + contract_address = unwrap_location(LLLnode.from_list(var.pos, typ=var.typ, location='storage', pos=getpos(self.stmt), annotation='self.' + self.stmt.func.value.attr)) + # import pdb; pdb.set_trace() return external_contract_call_stmt(self.stmt, self.context, contract_name, contract_address) elif isinstance(self.stmt.func, ast.Attribute) and self.stmt.func.value.id == 'log': if self.stmt.func.attr not in self.context.sigs['self']: diff --git a/viper/types.py b/viper/types.py index 5b51023ac9..731799697e 100644 --- a/viper/types.py +++ b/viper/types.py @@ -14,6 +14,8 @@ def print_unit(unit): if unit is None: return '*' + if not isinstance(unit, dict): + return unit pos = '' for k in sorted([x for x in unit.keys() if unit[x] > 0]): if unit[k] > 1: @@ -239,7 +241,7 @@ def parse_unit(item): # Parses an expression representing a type. Annotation refers to whether # the type is to be located in memory or storage -def parse_type(item, location): +def parse_type(item, location, sigs={}): # Base types, eg. num if isinstance(item, ast.Name): if item.id in base_types: @@ -248,13 +250,19 @@ def parse_type(item, location): return special_types[item.id] else: raise InvalidTypeException("Invalid base type: " + item.id, item) - # Units, eg. num (1/sec) + # Units, eg. num (1/sec) or contracts elif isinstance(item, ast.Call): + # Contract_types + if item.func.id == 'contract' or item.func.id == 'address': + if sigs and item.args[0].id in sigs: + return BaseType('address', item.args[0].id) + else: + raise InvalidTypeException('Invalid contract declaration') if not isinstance(item.func, ast.Name): raise InvalidTypeException("Malformed unit type:", item) base_type = item.func.id if base_type not in ('num', 'decimal'): - raise InvalidTypeException("Base type with units can only be num and decimal", item) + raise InvalidTypeException("Base type with units can only be num, decimal", item) if len(item.args) == 0: raise InvalidTypeException("Malformed unit type", item) if isinstance(item.args[-1], ast.Name) and item.args[-1].id == "positional": From 4abdd2b59b58fe87ca0aee05c792a6e0363b5358 Mon Sep 17 00:00:00 2001 From: Lane Rettig Date: Mon, 4 Dec 2017 09:36:04 +0800 Subject: [PATCH 118/162] Remove .python-version, update .gitignore --- .gitignore | 2 ++ .python-version | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 .python-version diff --git a/.gitignore b/.gitignore index 5c6db0540d..21f4d8a8ac 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ htmlcov/ docs/_build docs/modules.rst +# IDEs +.idea/ diff --git a/.python-version b/.python-version deleted file mode 100644 index d70c8f8d89..0000000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.6 From 0a134934acfbc888f4e59a95469d9a923823a189 Mon Sep 17 00:00:00 2001 From: sdtsui Date: Mon, 4 Dec 2017 14:31:27 -0500 Subject: [PATCH 119/162] Improve vipercoin example: link out to resources describing attack vectors --- examples/tokens/vipercoin.v.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/tokens/vipercoin.v.py b/examples/tokens/vipercoin.v.py index 2c9d8ba1ca..3767b6e387 100644 --- a/examples/tokens/vipercoin.v.py +++ b/examples/tokens/vipercoin.v.py @@ -86,12 +86,15 @@ def transferFrom(_from: address, _to: address, _value: num(num256)) -> bool: # Allow _spender to withdraw from your account, multiple times, up to the _value amount. # If this function is called again it overwrites the current allowance with _value. # -# NOTE: To prevent attack vectors like the one described here and discussed here, -# clients SHOULD make sure to create user interfaces in such a way that they +# NOTE: We would like to prevent attack vectors like the one described here: +# https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit#heading=h.m9fhqynw2xvt +# and discussed here: +# https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 +# +# Clients SHOULD make sure to create user interfaces in such a way that they # set the allowance first to 0 before setting it to another value for the # same spender. THOUGH The contract itself shouldn't enforce it, to allow # backwards compatilibilty with contracts deployed before. -# @public def approve(_spender: address, _amount: num(num256)) -> bool: @@ -101,7 +104,7 @@ def approve(_spender: address, _amount: num(num256)) -> bool: return True -# Get the allowance an address has to spend anothers' token. +# Get the allowance an address has to spend another's token. @public def allowance(_owner: address, _spender: address) -> num256: From f477f77b4af1d7fd3170d9ee35dcbd0c51f8b05b Mon Sep 17 00:00:00 2001 From: David Knott Date: Tue, 28 Nov 2017 13:38:42 -0700 Subject: [PATCH 120/162] Test the contract data type --- .../test_external_contract_calls.py | 170 +++++++++++++++++- viper/parser/stmt.py | 1 - 2 files changed, 169 insertions(+), 2 deletions(-) diff --git a/tests/parser/features/external_contracts/test_external_contract_calls.py b/tests/parser/features/external_contracts/test_external_contract_calls.py index 21ad245a7d..65fbfde803 100644 --- a/tests/parser/features/external_contracts/test_external_contract_calls.py +++ b/tests/parser/features/external_contracts/test_external_contract_calls.py @@ -3,6 +3,7 @@ get_contract_with_gas_estimation, get_contract from viper.exceptions import StructureException, VariableDeclarationException, InvalidTypeException + def test_external_contract_calls(): contract_1 = """ @public @@ -161,8 +162,9 @@ def set_lucky(_lucky: num): lucky: public(num) @public -def set_lucky(_lucky: num): +def set_lucky(_lucky: num) -> num: self.lucky = _lucky + return self.lucky """ lucky_number_2 = 3 @@ -375,3 +377,169 @@ class Foo(): def foo(arg2: num) -> num: pass """ assert_tx_failed(lambda: get_contract(contract), exception = StructureException) + + +def test_external_contract_call_declaration_expr(): + contract_1 = """ +@public +def bar() -> num: + return 1 +""" + + contract_2 = """ +class Bar(): + def bar() -> num: pass + +bar_contract: Bar + +@public +def foo(contract_address: contract(Bar)) -> num: + self.bar_contract = contract_address + return self.bar_contract.bar() + """ + + c1 = get_contract(contract_1) + c2 = get_contract(contract_2) + assert c2.foo(c1.address) == 1 + + +def test_external_contract_call_declaration_stmt(): + contract_1 = """ +lucky: num + +@public +def set_lucky(_lucky: num): + self.lucky = _lucky + +@public +def get_lucky() -> num: + return self.lucky +""" + + contract_2 = """ +class Bar(): + def set_lucky(arg1: num): pass + def get_lucky() -> num: pass + +bar_contract: Bar + +@public +def set_lucky(contract_address: contract(Bar)): + self.bar_contract = contract_address + self.bar_contract.set_lucky(1) + +@public +def get_lucky(contract_address: contract(Bar)) -> num: + self.bar_contract = contract_address + return self.bar_contract.get_lucky() + """ + + c1 = get_contract(contract_1) + c2 = get_contract(contract_2) + assert c1.get_lucky() == 0 + assert c2.get_lucky(c1.address) == 0 + c1.set_lucky(6) + assert c1.get_lucky() == 6 + assert c2.get_lucky(c1.address) == 6 + c2.set_lucky(c1.address) + assert c1.get_lucky() == 1 + assert c2.get_lucky(c1.address) == 1 + +def test_complex_external_contract_call_declaration(): + contract_1 = """ +@public +def get_lucky() -> num: + return 1 +""" + + contract_2 = """ +@public +def get_lucky() -> num: + return 2 +""" + + contract_3 = """ +class Bar(): + def set_lucky(arg1: num): pass + def get_lucky() -> num: pass + +bar_contract: Bar + +@public +def set_contract(contract_address: contract(Bar)): + self.bar_contract = contract_address + +@public +def get_lucky() -> num: + return self.bar_contract.get_lucky() +""" + + c1 = get_contract_with_gas_estimation(contract_1) + c2 = get_contract_with_gas_estimation(contract_2) + c3 = get_contract_with_gas_estimation(contract_3) + assert c1.get_lucky() == 1 + assert c2.get_lucky() == 2 + c3.set_contract(c1.address) + assert c3.get_lucky() == 1 + c3.set_contract(c2.address) + assert c3.get_lucky() == 2 + + +def test_address_can_returned_from_contract_type(t): + contract_1 = """ +@public +def bar() -> num: + return 1 +""" + contract_2 = """ +class Bar(): + def bar() -> num: pass + +bar_contract: public(Bar) + +@public +def foo(contract_address: contract(Bar)) -> num: + self.bar_contract = contract_address + +@public +def get_bar() -> num: + return self.bar_contract.bar() +""" + c1 = get_contract(contract_1) + c2 = get_contract(contract_2) + c2.foo(c1.address) + assert u.remove_0x_head(c2.get_bar_contract()) == c1.address.hex() + assert c2.get_bar() == 1 + + + +def test_invalid_external_contract_call_declaration_1(assert_compile_failed): + contract_1 = """ +class Bar(): + def bar() -> num: pass + +bar_contract: Bar + +@public +def foo(contract_address: contract(Boo)) -> num: + self.bar_contract = contract_address + return self.bar_contract.bar() + """ + + assert_compile_failed(lambda: get_contract(contract_1), InvalidTypeException) + + +def test_invalid_external_contract_call_declaration_2(assert_compile_failed): + contract_1 = """ +class Bar(): + def bar() -> num: pass + +bar_contract: Boo + +@public +def foo(contract_address: contract(Bar)) -> num: + self.bar_contract = contract_address + return self.bar_contract.bar() + """ + + assert_compile_failed(lambda: get_contract(contract_1), InvalidTypeException) diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index aa9becef9f..b05e3c77d2 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -136,7 +136,6 @@ def call(self): contract_name = self.context.globals[self.stmt.func.value.attr].typ.unit var = self.context.globals[self.stmt.func.value.attr] contract_address = unwrap_location(LLLnode.from_list(var.pos, typ=var.typ, location='storage', pos=getpos(self.stmt), annotation='self.' + self.stmt.func.value.attr)) - # import pdb; pdb.set_trace() return external_contract_call_stmt(self.stmt, self.context, contract_name, contract_address) elif isinstance(self.stmt.func, ast.Attribute) and self.stmt.func.value.id == 'log': if self.stmt.func.attr not in self.context.sigs['self']: From 7336d656f89c5c8d86871d7d56bb751cbe447097 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Mon, 4 Dec 2017 20:29:21 -0800 Subject: [PATCH 121/162] Update version of ethereum --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a3d64bf334..5fadabc526 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ license=license, packages=find_packages(exclude=('tests', 'docs')), install_requires=[ - 'ethereum==2.1.0', + 'ethereum==2.1.3', 'bumpversion', 'pytest-cov', 'pytest-runner', # Must be after pytest-cov or it will not work From 2e74e11ad5bed65c4c379cf2fcf199b88d34addc Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Wed, 6 Dec 2017 12:18:02 +0200 Subject: [PATCH 122/162] Improves gas estimate on internal call. --- tests/parser/features/test_internal_call.py | 4 ++-- viper/parser/expr.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/parser/features/test_internal_call.py b/tests/parser/features/test_internal_call.py index 1baa345feb..677d847543 100644 --- a/tests/parser/features/test_internal_call.py +++ b/tests/parser/features/test_internal_call.py @@ -39,7 +39,7 @@ def return_hash_of_rzpadded_cow() -> bytes32: return self._hashy(0x636f770000000000000000000000000000000000000000000000000000000000) """ - c = get_contract(selfcall_code_2) + c = get_contract_with_gas_estimation(selfcall_code_2) assert c.returnten() == 10 assert c.return_hash_of_rzpadded_cow() == u.sha3(b'cow' + b'\x00' * 29) @@ -107,7 +107,7 @@ def return_goose2() -> bytes <= 10: return self.slicey2(5, "goosedog") """ - c = get_contract(selfcall_code_4) + c = get_contract_with_gas_estimation(selfcall_code_4) assert c.returnten() == 10 assert c.return_mongoose() == b"mongoose" assert c.return_goose() == b"goose" diff --git a/viper/parser/expr.py b/viper/parser/expr.py index 27df3fbb95..f7d4fb73f9 100644 --- a/viper/parser/expr.py +++ b/viper/parser/expr.py @@ -444,6 +444,7 @@ def call(self): raise VariableDeclarationException("Function not declared yet (reminder: functions cannot " "call functions later in code than themselves): %s" % self.expr.func.attr) sig = self.context.sigs['self'][self.expr.func.attr] + add_gas = self.context.sigs['self'][method_name].gas # gas of call inargs, inargsize = pack_arguments(sig, [Expr(arg, self.context).lll_node for arg in self.expr.args], self.context) output_placeholder = self.context.new_placeholder(typ=sig.output_type) if isinstance(sig.output_type, BaseType): @@ -456,7 +457,7 @@ def call(self): ['assert', ['call', ['gas'], ['address'], 0, inargs, inargsize, output_placeholder, get_size_of_type(sig.output_type) * 32]], - returner], typ=sig.output_type, location='memory', pos=getpos(self.expr)) + returner], typ=sig.output_type, location='memory', pos=getpos(self.expr), add_gas_estimate=add_gas) o.gas += sig.gas return o elif isinstance(self.expr.func, ast.Attribute) and isinstance(self.expr.func.value, ast.Call): From aeb1fbe61f12df7bfa0891ed2a2c72d68322251b Mon Sep 17 00:00:00 2001 From: Bryant Date: Wed, 6 Dec 2017 18:52:01 -0500 Subject: [PATCH 123/162] Removed suicide keyword --- docs/old_readme.txt | 2 +- viper/functions.py | 1 - viper/opcodes.py | 1 - viper/utils.py | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/old_readme.txt b/docs/old_readme.txt index 6463d54e22..e250137bb3 100644 --- a/docs/old_readme.txt +++ b/docs/old_readme.txt @@ -62,7 +62,7 @@ Note that not all programs that satisfy the following are valid; for example, th OR break OR return OR send(, ) - OR selfdestruct() # suicide() is a synonym + OR selfdestruct() OR [other functions, see full list in viper/functions.py] var = OR . diff --git a/viper/functions.py b/viper/functions.py index df57329140..fdfddbd4ba 100644 --- a/viper/functions.py +++ b/viper/functions.py @@ -846,7 +846,6 @@ def minmax(expr, args, kwargs, context, is_min): stmt_dispatch_table = { 'send': send, - 'suicide': selfdestruct, 'selfdestruct': selfdestruct, 'raw_call': raw_call, 'raw_log': raw_log, diff --git a/viper/opcodes.py b/viper/opcodes.py index 4c059b4edb..7d5735a3da 100644 --- a/viper/opcodes.py +++ b/viper/opcodes.py @@ -68,7 +68,6 @@ 'SELFDESTRUCT': [0xff, 1, 0, 25000], 'STATICCALL': [0xfa, 6, 1, 40], 'REVERT': [0xfd, 2, 0, 0], - 'SUICIDE': [0xff, 1, 0, 5000], 'INVALID': [0xfe, 0, 0, 0], } diff --git a/viper/utils.py b/viper/utils.py index 2f449ff64f..7002ab151b 100644 --- a/viper/utils.py +++ b/viper/utils.py @@ -120,7 +120,7 @@ class SizeLimits: reserved_words = ['int128', 'int256', 'uint256', 'address', 'bytes32', 'real', 'real128x128', 'if', 'for', 'while', 'until', 'pass', 'def', 'push', 'dup', 'swap', 'send', 'call', - 'suicide', 'selfdestruct', 'assert', 'stop', 'throw', + 'selfdestruct', 'assert', 'stop', 'throw', 'raise', 'init', '_init_', '___init___', '____init____', 'true', 'false', 'self', 'this', 'continue', 'ether', 'wei', 'finney', 'szabo', 'shannon', 'lovelace', 'ada', From ccc53b2d60890d52c7f4b67fb9b243ae5e71da35 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Thu, 7 Dec 2017 14:42:02 +0200 Subject: [PATCH 124/162] Carry over add_gas_estimate in optimizer. --- viper/optimizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/viper/optimizer.py b/viper/optimizer.py index bd339c1805..5ec2f00382 100644 --- a/viper/optimizer.py +++ b/viper/optimizer.py @@ -106,9 +106,9 @@ def optimize(node): o.append(arg) return LLLnode(node.value, o, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate) elif hasattr(node, 'total_gas'): - o = LLLnode(node.value, argz, node.typ, node.location, node.pos, node.annotation) + o = LLLnode(node.value, argz, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate) o.total_gas = node.total_gas - node.gas + o.gas o.func_name = node.func_name return o else: - return LLLnode(node.value, argz, node.typ, node.location, node.pos, node.annotation) + return LLLnode(node.value, argz, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate) From 4b0fbbafa104877c955f7a6d594e128fc3562b23 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Thu, 7 Dec 2017 14:44:03 +0200 Subject: [PATCH 125/162] Adds gas estimation for internal calls. --- tests/parser/features/test_internal_call.py | 4 ++-- viper/functions.py | 2 +- viper/parser/expr.py | 3 ++- viper/parser/stmt.py | 6 ++++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/parser/features/test_internal_call.py b/tests/parser/features/test_internal_call.py index 677d847543..8d58ec63c3 100644 --- a/tests/parser/features/test_internal_call.py +++ b/tests/parser/features/test_internal_call.py @@ -130,7 +130,7 @@ def returnten() -> num: self.increment() return self.counter """ - c = get_contract(selfcall_code_5) + c = get_contract_with_gas_estimation(selfcall_code_5) assert c.returnten() == 10 print("Passed self-call statement test") @@ -158,7 +158,7 @@ def return_mongoose_revolution_32_excls() -> bytes <= 201: return self.hardtest("megamongoose123", 4, 8, concat("russian revolution", self.excls), 8, 42) """ - c = get_contract(selfcall_code_6) + c = get_contract_with_gas_estimation(selfcall_code_6) assert c.return_mongoose_revolution_32_excls() == b"mongoose_revolution" + b"!" * 32 print("Passed composite self-call test") diff --git a/viper/functions.py b/viper/functions.py index df57329140..4e676c5dad 100644 --- a/viper/functions.py +++ b/viper/functions.py @@ -289,7 +289,7 @@ def concat(expr, context): # Memory location of the output seq.append(placeholder) return LLLnode.from_list( - ['with', '_poz', 0, ['seq'] + seq], typ=ByteArrayType(total_maxlen), location='memory', pos=getpos(expr) + ['with', '_poz', 0, ['seq'] + seq], typ=ByteArrayType(total_maxlen), location='memory', pos=getpos(expr), annotation='concat' ) diff --git a/viper/parser/expr.py b/viper/parser/expr.py index f7d4fb73f9..7f4b378b8b 100644 --- a/viper/parser/expr.py +++ b/viper/parser/expr.py @@ -457,7 +457,8 @@ def call(self): ['assert', ['call', ['gas'], ['address'], 0, inargs, inargsize, output_placeholder, get_size_of_type(sig.output_type) * 32]], - returner], typ=sig.output_type, location='memory', pos=getpos(self.expr), add_gas_estimate=add_gas) + returner], typ=sig.output_type, location='memory', + pos=getpos(self.expr), add_gas_estimate=add_gas, annotation='Internal Call: %s' % method_name) o.gas += sig.gas return o elif isinstance(self.expr.func, ast.Attribute) and isinstance(self.expr.func.value, ast.Call): diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index ecf21e22d1..a48322c8ff 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -116,14 +116,16 @@ def call(self): if isinstance(self.stmt.func, ast.Name) and self.stmt.func.id in stmt_dispatch_table: return stmt_dispatch_table[self.stmt.func.id](self.stmt, self.context) elif isinstance(self.stmt.func, ast.Attribute) and isinstance(self.stmt.func.value, ast.Name) and self.stmt.func.value.id == "self": - if self.stmt.func.attr not in self.context.sigs['self']: + method_name = self.stmt.func.attr + if method_name not in self.context.sigs['self']: raise VariableDeclarationException("Function not declared yet (reminder: functions cannot " "call functions later in code than themselves): %s" % self.stmt.func.attr) + add_gas = self.context.sigs['self'][method_name].gas inargs, inargsize = pack_arguments(self.context.sigs['self'][self.stmt.func.attr], [Expr(arg, self.context).lll_node for arg in self.stmt.args], self.context) return LLLnode.from_list(['assert', ['call', ['gas'], ['address'], 0, inargs, inargsize, 0, 0]], - typ=None, pos=getpos(self.stmt)) + typ=None, pos=getpos(self.stmt), add_gas_estimate=add_gas, annotation='Internal Call: %s' % method_name) elif isinstance(self.stmt.func, ast.Attribute) and isinstance(self.stmt.func.value, ast.Call): contract_name = self.stmt.func.value.func.id contract_address = Expr.parse_value_expr(self.stmt.func.value.args[0], self.context) From d658ae769690842c9a378a16361a14981db590c2 Mon Sep 17 00:00:00 2001 From: Bryant Date: Thu, 7 Dec 2017 21:45:10 -0500 Subject: [PATCH 126/162] Added test for invalid keyword --- tests/parser/syntax/test_invalids.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/parser/syntax/test_invalids.py b/tests/parser/syntax/test_invalids.py index 3e794b2250..3ce25fda63 100644 --- a/tests/parser/syntax/test_invalids.py +++ b/tests/parser/syntax/test_invalids.py @@ -237,6 +237,13 @@ def foo(): return 3 """, TypeMismatchException) +# We disabled these keywords +# throws AttributeError in this case +must_fail(""" +@public +def foo(): + suicide(msg.sender) + """, AttributeError) @pytest.mark.parametrize('bad_code,exception_type', fail_list) def test_compilation_fails_with_exception(bad_code, exception_type): From 9b2da7cb2e53cd35a9d95c524e748ca57e874165 Mon Sep 17 00:00:00 2001 From: James Ray Date: Fri, 8 Dec 2017 15:39:11 +1100 Subject: [PATCH 127/162] not official a language > officially --- docs/compiling-a-contract.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/compiling-a-contract.rst b/docs/compiling-a-contract.rst index 16d89e1fba..a854101c53 100644 --- a/docs/compiling-a-contract.rst +++ b/docs/compiling-a-contract.rst @@ -6,7 +6,7 @@ To compile a contract, use: viper yourFileName.v.py .. note:: - Since .vy is not official a language supported by any syntax highlighters or linters, + Since .vy is not officially a language supported by any syntax highlighters or linters, it is recommended to name your Viper file ending with `.v.py` in order to have Python syntax highlighting. An `online compiler `_ is available as well, which lets you experiment with From 0691b1d1acf5f0f8d3112c47fa36abf574f68bb2 Mon Sep 17 00:00:00 2001 From: James Ray Date: Fri, 8 Dec 2017 18:09:12 +1100 Subject: [PATCH 128/162] Changing notes to be indented blocks starting on the line after .. note:: https://sublime-and-sphinx-guide.readthedocs.io/en/latest/notes_warnings.html --- docs/viper-by-example.rst | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/viper-by-example.rst b/docs/viper-by-example.rst index a0ad6bf7be..a8c5317e17 100644 --- a/docs/viper-by-example.rst +++ b/docs/viper-by-example.rst @@ -119,10 +119,11 @@ design patterns and features of the Viper language. And of course, no smart contract tutorial is complete without a note on security. -.. note:: It's always important to keep security in mind when designing a smart -contract. As any application becomes more complex, the greater the potential for -introducing new risks. Thus, it's always good practice to keep contracts as -readable and simple as possible. +.. note:: + It's always important to keep security in mind when designing a smart + contract. As any application becomes more complex, the greater the potential for + introducing new risks. Thus, it's always good practice to keep contracts as + readable and simple as possible. Whenever you're ready, let's turn it up a notch in the next example. @@ -314,10 +315,11 @@ period is over and that the balance has reached/passed its set goal. If those two conditions pass, the contract calls the ``selfdestruct()`` function and sends the collected funds to the beneficiary. -.. note:: Notice that we have access to the total amount sent to the contract by -calling ``self.balance``, a variable we never explicitly set. Similar to ``msg`` -and ``block``, ``self.balance`` is a built-in variable thats available in all -Viper contracts. +.. note:: + Notice that we have access to the total amount sent to the contract by + calling ``self.balance``, a variable we never explicitly set. Similar to ``msg`` + and ``block``, ``self.balance`` is a built-in variable thats available in all + Viper contracts. We can finalize the campaign if all goes well, but what happens if the crowdfunding campaign isn't successful? We're going to need a way to refund From 699ccfa63b26b815b0c1374de9e885b00e593c07 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Fri, 8 Dec 2017 13:44:44 +0200 Subject: [PATCH 129/162] Adds storage list logging support. --- tests/parser/features/test_logging.py | 22 ++++++++++++++++++++++ viper/parser/parser.py | 17 ++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index 135e753e9b..6fb2004bca 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -542,6 +542,28 @@ def foo(): assert get_last_log(t, c)["_value"] == [1, 2, 3, 4] +def test_storage_list_packing(get_last_log): + code = """ +Bar: __log__({_value: num[4]}) +x: num[4] + +@public +def foo(): + log.Bar(self.x) + +@public +def set_list(): + self.x = [1, 2, 3, 4] + """ + c = get_contract_with_gas_estimation(code) + + c.foo() + assert get_last_log(t, c)["_value"] == [0, 0, 0, 0] + c.set_list() + c.foo() + assert get_last_log(t, c)["_value"] == [1, 2, 3, 4] + + def test_passed_list_packing(get_last_log): code = """ Bar: __log__({_value: num[4]}) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 09aed2bf05..41ae80de5b 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -703,9 +703,24 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): maxlen += (typ.count - 1) * 32 typ = typ.subtype - if isinstance(arg, ast.Name): + def check_list_type_match(provided): # Check list types match. + if provided != typ: + raise TypeMismatchException( + "Log list type '%s' does not match provided, expected '%s'" % (provided, typ) + ) + + if isinstance(arg, ast.Attribute) and arg.value.id == 'self': # List from storage + stor_list = context.globals[arg.attr] + check_list_type_match(stor_list.typ.subtype) + size = stor_list.typ.count + for offset in range(0, size): + arg2 = LLLnode.from_list(['sload', ['add', ['sha3_32', Expr(arg, context).lll_node], offset]], + typ=typ) + holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32))) + elif isinstance(arg, ast.Name): # List from variable. size = context.vars[arg.id].size pos = context.vars[arg.id].pos + check_list_type_match(context.vars[arg.id].typ.subtype) for i in range(0, size): offset = 32 * i arg2 = LLLnode.from_list(pos + offset, typ=typ, location='memory') From 96461a92063ea08ae5a4aa0d51c8e860602542ff Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Fri, 8 Dec 2017 13:48:11 +0200 Subject: [PATCH 130/162] Adds test to check list logging is of correct (sub)type. --- tests/parser/syntax/test_logging.py | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/parser/syntax/test_logging.py diff --git a/tests/parser/syntax/test_logging.py b/tests/parser/syntax/test_logging.py new file mode 100644 index 0000000000..4b0f6c4c80 --- /dev/null +++ b/tests/parser/syntax/test_logging.py @@ -0,0 +1,32 @@ +import pytest +from pytest import raises + +from viper import compiler +from viper.exceptions import TypeMismatchException + + +fail_list = [ + """ +Bar: __log__({_value: num[4]}) +x: decimal[4] + +@public +def foo(): + log.Bar(self.x) + """, + """ +Bar: __log__({_value: num[4]}) + +@public +def foo(): + x: decimal[4] + log.Bar(x) + """ +] + + +@pytest.mark.parametrize('bad_code', fail_list) +def test_logging_fail(bad_code): + + with raises(TypeMismatchException): + compiler.compile(bad_code) From eabcee49e6a31aac9ec81baeb6201174368f9e5a Mon Sep 17 00:00:00 2001 From: James Ray Date: Sat, 9 Dec 2017 17:13:56 +1100 Subject: [PATCH 131/162] Splitting comments over two lines where necessary to fit on the page on readthedocs --- .../safe_remote_purchase.v.py | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/examples/safe_remote_purchase/safe_remote_purchase.v.py b/examples/safe_remote_purchase/safe_remote_purchase.v.py index 94fb96def0..a63bcb5410 100644 --- a/examples/safe_remote_purchase/safe_remote_purchase.v.py +++ b/examples/safe_remote_purchase/safe_remote_purchase.v.py @@ -1,11 +1,17 @@ -#Safe Remote Purchase (https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst) ported to viper and optimized +#Safe Remote Purchase +#Originally from +#https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst +#ported to viper and optimized #Rundown of the transaction: -#1. Seller posts item for sale and posts safety deposit of double the item value. Balance is 2*value. +#1. Seller posts item for sale and posts safety deposit of double the item value. +# Balance is 2*value. #(1.1. Seller can reclaim deposit and close the sale as long as nothing was purchased.) -#2. Buyer purchases item (value) plus posts an additional safety deposit (Item value). Balance is 4*value -#3. Seller ships item -#4. Buyer confirms receiving the item. Buyer's deposit (value) is returned. Seller's deposit (2*value) + items value is returned. Balance is 0. +#2. Buyer purchases item (value) plus posts an additional safety deposit (Item value). +# Balance is 4*value. +#3. Seller ships item. +#4. Buyer confirms receiving the item. Buyer's deposit (value) is returned. +#Seller's deposit (2*value) + items value is returned. Balance is 0. value: public(wei_value) #Value of the item seller: public(address) @@ -14,32 +20,36 @@ #@constant #def unlocked() -> bool: #Is a refund possible for the seller? # return (self.balance == self.value*2) -# + @public @payable def __init__(): assert (msg.value % 2) == 0 - self.value = msg.value / 2 #Seller initializes contract by posting a safety deposit of 2*value of the item up for sale + self.value = msg.value / 2 #The seller initializes the contract by + #posting a safety deposit of 2*value of the item up for sale. self.seller = msg.sender self.unlocked = true @public def abort(): - assert self.unlocked #Is the contract still refundable - assert msg.sender == self.seller #Only seller can refund his deposit before any buyer purchases the item - selfdestruct(self.seller) #Refunds seller, deletes contract + assert self.unlocked #Is the contract still refundable? + assert msg.sender == self.seller #Only the seller can refund + # his deposit before any buyer purchases the item. + selfdestruct(self.seller) #Refunds the seller and deletes the contract. @public @payable def purchase(): - assert self.unlocked #Contract still open (item still up for sale)? - assert msg.value == (2*self.value) #Is the deposit of correct value? + assert self.unlocked #Is the contract still open (is the item still up for sale)? + assert msg.value == (2*self.value) #Is the deposit the correct value? self.buyer = msg.sender self.unlocked = false @public def received(): - assert not self.unlocked #Is the item already purchased and pending confirmation of buyer + assert not self.unlocked #Is the item already purchased and pending confirmation + # from the buyer? assert msg.sender == self.buyer - send(self.buyer, self.value) #Return deposit (=value) to buyer - selfdestruct(self.seller) #Returns deposit (=2*value) and the purchase price (=value) + send(self.buyer, self.value) #Return the buyer's deposit (=value) to the buyer. + selfdestruct(self.seller) #Return the seller's deposit (=2*value) + # and the purchase price (=value) to the seller. From d23624b3309205845c1aad6e41a268b42ae40e67 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Sat, 9 Dec 2017 19:02:10 -0800 Subject: [PATCH 132/162] Update to use conftest; fix some incorrect tests --- .gitignore | 1 + tests/conftest.py | 116 +++++++++++++ tests/examples/company/test_company.py | 4 +- .../test_on_chain_market_maker.py | 40 ++--- .../test_safe_remote_purchase.py | 1 - tests/examples/tokens/test_vipercoin.py | 4 - tests/examples/wallet/test_wallet.py | 2 - .../parser/features/arithmetic/test_modulo.py | 12 +- .../features/decorators/test_constant.py | 7 +- .../features/decorators/test_private.py | 7 +- .../parser/features/decorators/test_public.py | 8 +- .../external_contracts/test_erc20_abi.py | 16 +- .../test_external_contract_calls.py | 51 +++--- tests/parser/features/iteration/test_break.py | 11 +- .../features/iteration/test_for_in_list.py | 39 ++--- .../features/iteration/test_range_in.py | 13 +- .../features/iteration/test_repeater.py | 15 +- tests/parser/features/test_assert.py | 4 +- tests/parser/features/test_assignment.py | 9 +- tests/parser/features/test_bytes_map_keys.py | 12 +- tests/parser/features/test_clampers.py | 7 +- tests/parser/features/test_comments.py | 7 +- tests/parser/features/test_conditionals.py | 7 +- tests/parser/features/test_constructor.py | 15 +- tests/parser/features/test_gas.py | 6 +- tests/parser/features/test_internal_call.py | 21 +-- tests/parser/features/test_logging.py | 164 +++++++++--------- tests/parser/features/test_packing.py | 7 +- tests/parser/functions/rlp/conftest.py | 28 +++ .../parser/functions/rlp/test_block_number.py | 10 ++ .../functions/{ => rlp}/test_rlp_list.py | 7 +- tests/parser/functions/test_as_num256.py | 8 +- tests/parser/functions/test_bitwise.py | 7 +- tests/parser/functions/test_block_number.py | 13 -- tests/parser/functions/test_concat.py | 15 +- tests/parser/functions/test_ec.py | 25 ++- tests/parser/functions/test_ecrecover.py | 13 +- tests/parser/functions/test_extract32.py | 9 +- tests/parser/functions/test_is_contract.py | 7 +- tests/parser/functions/test_length.py | 7 +- tests/parser/functions/test_method_id.py | 7 +- tests/parser/functions/test_minmax.py | 7 +- tests/parser/functions/test_raw_call.py | 17 +- tests/parser/functions/test_return_tuple.py | 9 +- tests/parser/functions/test_send.py | 9 +- tests/parser/functions/test_sha3.py | 15 +- tests/parser/functions/test_slice.py | 13 +- tests/parser/globals/test_getters.py | 9 +- tests/parser/globals/test_globals.py | 6 +- tests/parser/globals/test_setters.py | 13 +- tests/parser/integration/test_basics.py | 13 +- tests/parser/integration/test_crowdfund.py | 33 ++-- tests/parser/integration/test_escrow.py | 9 +- tests/parser/types/numbers/test_decimals.py | 13 +- tests/parser/types/numbers/test_num.py | 17 +- tests/parser/types/numbers/test_num256.py | 21 +-- tests/parser/types/test_bytes.py | 19 +- tests/parser/types/test_lists.py | 13 +- tests/parser/types/test_string_literal.py | 9 +- tests/parser/types/value/test_wei.py | 7 +- tests/setup_transaction_tests.py | 148 ---------------- 61 files changed, 476 insertions(+), 686 deletions(-) create mode 100644 tests/parser/functions/rlp/conftest.py create mode 100644 tests/parser/functions/rlp/test_block_number.py rename tests/parser/functions/{ => rlp}/test_rlp_list.py (93%) delete mode 100644 tests/parser/functions/test_block_number.py delete mode 100644 tests/setup_transaction_tests.py diff --git a/.gitignore b/.gitignore index 21f4d8a8ac..81eb1cbeeb 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ docs/modules.rst # IDEs .idea/ +*.iml \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 392e9b6989..ce7986ff62 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,6 @@ import pytest +from functools import wraps + from ethereum.tools import tester from viper.parser.parser_utils import ( LLLnode @@ -7,6 +9,45 @@ compile_lll, optimizer ) +from ethereum import utils as ethereum_utils + +@pytest.fixture +def check_gas(chain): + def check_gas(code, func=None, num_txs=1): + if func: + gas_estimate = tester.languages['viper'].gas_estimate(code)[func] + else: + gas_estimate = sum(tester.languages['viper'].gas_estimate(code).values()) + gas_actual = chain.head_state.receipts[-1].gas_used \ + - chain.head_state.receipts[-1-num_txs].gas_used \ + - chain.last_tx.intrinsic_gas_used*num_txs + + # Computed upper bound on the gas consumption should + # be greater than or equal to the amount of gas used + if gas_estimate < gas_actual: + raise Exception("Gas upper bound fail: bound %d actual %d" % (gas_estimate, gas_actual)) + + print('Function name: {} - Gas estimate {}, Actual: {}'.format( + func, gas_estimate, gas_actual) + ) + return check_gas + +def gas_estimation_decorator(chain, fn, source_code, func): + def decorator(*args, **kwargs): + @wraps(fn) + def decorated_function(*args, **kwargs): + result = fn(*args, **kwargs) + check_gas(chain)(source_code, func) + return result + return decorated_function(*args, **kwargs) + return decorator + +def set_decorator_to_contract_function(chain, contract, source_code, func): + func_definition = getattr(contract, func) + func_with_decorator = gas_estimation_decorator( + chain, func_definition, source_code, func + ) + setattr(contract, func, func_with_decorator) @pytest.fixture def bytes_helper(): @@ -19,6 +60,16 @@ def t(): tester.s = tester.Chain() return tester +@pytest.fixture(scope="module") +def chain(): + s = tester.Chain() + s.head_state.gas_limit = 10**9 + return s + +@pytest.fixture +def utils(): + return ethereum_utils + @pytest.fixture def get_contract_from_lll(t): def lll_compiler(lll): @@ -27,6 +78,46 @@ def lll_compiler(lll): t.s.tx(to=b'', data=byte_code) return lll_compiler +@pytest.fixture +def get_contract_with_gas_estimation(chain): + def get_contract_with_gas_estimation( + source_code, + *args, **kwargs): + contract = chain.contract(source_code, language="viper", *args, **kwargs) + for func_name in contract.translator.function_data: + set_decorator_to_contract_function( + chain, contract, source_code, func_name + ) + return contract + + return get_contract_with_gas_estimation + +@pytest.fixture +def get_contract_with_gas_estimation_for_constants(chain): + def get_contract_with_gas_estimation_for_constants( + source_code, + *args, **kwargs): + abi = tester.languages['viper'].mk_full_signature(source_code) + # Take out constants from the abi for the purpose of gas estimation + for func in abi: + func['constant'] = False + ct = tester.ContractTranslator(abi) + byte_code = tester.languages['viper'].compile(source_code) + (ct.encode_constructor_arguments(kwargs['args']) if kwargs else b'') + address = chain.tx(to=b'', data=byte_code) + contract = tester.ABIContract(chain, abi, address) + for func_name in contract.translator.function_data: + set_decorator_to_contract_function( + chain, contract, source_code, func_name + ) + return contract + return get_contract_with_gas_estimation_for_constants + +@pytest.fixture +def get_contract(chain): + def get_contract(source_code, *args, **kwargs): + return chain.contract(source_code, language="viper", *args, **kwargs) + return get_contract + @pytest.fixture def assert_tx_failed(t): def assert_tx_failed(function_to_test, exception = tester.TransactionFailed): @@ -42,3 +133,28 @@ def assert_compile_failed(function_to_test, exception = tester.TransactionFailed with pytest.raises(exception): function_to_test() return assert_compile_failed + +@pytest.fixture +def get_logs(): + def get_logs(receipt, contract, event_name=None): + contract_log_ids = contract.translator.event_data.keys() # All the log ids contract has + # All logs originating from contract, and matching event_name (if specified) + logs = [log for log in receipt.logs \ + if log.topics[0] in contract_log_ids and \ + log.address == contract.address and \ + (not event_name or \ + contract.translator.event_data[log.topics[0]]['name'] == event_name)] + assert len(logs) > 0, "No logs in last receipt" + + # Return all events decoded in the receipt + return [contract.translator.decode_event(log.topics, log.data) for log in logs] + return get_logs + +@pytest.fixture +def get_last_log(get_logs): + def get_last_log(tester, contract, event_name=None): + receipt = tester.s.head_state.receipts[-1] # Only the receipts for the last block + # Get last log event with correct name and return the decoded event + print(get_logs(receipt, contract, event_name=event_name)) + return get_logs(receipt, contract, event_name=event_name)[-1] + return get_last_log \ No newline at end of file diff --git a/tests/examples/company/test_company.py b/tests/examples/company/test_company.py index 0b2dbd91a0..d2e5dc549b 100644 --- a/tests/examples/company/test_company.py +++ b/tests/examples/company/test_company.py @@ -1,9 +1,7 @@ import pytest from ethereum.tools import tester as t -from ethereum import utils -from tests.setup_transaction_tests import assert_tx_failed, ethereum_utils as u, get_logs from viper import compiler @pytest.fixture @@ -90,7 +88,7 @@ def test_valuation(tester): tester.c.buy_stock(sender=t.k1, value=test_value) assert tester.c.debt() == test_value -def test_logs(tester): +def test_logs(tester, get_logs): # Buy is logged tester.c.buy_stock(sender=t.k1, value=7 * tester.c.get_price()) receipt = tester.s.head_state.receipts[-1] diff --git a/tests/examples/market_maker/test_on_chain_market_maker.py b/tests/examples/market_maker/test_on_chain_market_maker.py index 5621d47a6e..4a67343dfb 100644 --- a/tests/examples/market_maker/test_on_chain_market_maker.py +++ b/tests/examples/market_maker/test_on_chain_market_maker.py @@ -1,13 +1,11 @@ import pytest from viper import compiler -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract @pytest.fixture -def market_maker(): +def market_maker(t, chain): t.languages['viper'] = compiler.Compiler() contract_code = open('examples/market_maker/on_chain_market_maker.v.py').read() - return s.contract(contract_code, language='viper') + return chain.contract(contract_code, language='viper') TOKEN_NAME = "Vipercoin" TOKEN_SYMBOL = "FANG" @@ -16,32 +14,32 @@ def market_maker(): TOKEN_TOTAL_SUPPLY = TOKEN_INITIAL_SUPPLY * (10 ** TOKEN_DECIMALS) @pytest.fixture -def erc20(): +def erc20(t, chain): t.languages['viper'] = compiler.Compiler() contract_code = open('examples/tokens/vipercoin.v.py').read() - return s.contract(contract_code, language='viper', args=[TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, TOKEN_INITIAL_SUPPLY]) + return chain.contract(contract_code, language='viper', args=[TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, TOKEN_INITIAL_SUPPLY]) -def test_initial_statet(market_maker): +def test_initial_statet(market_maker, utils): assert market_maker.get_total_eth_qty() == 0 assert market_maker.get_total_token_qty() == 0 assert market_maker.get_invariant() == 0 - assert u.remove_0x_head(market_maker.get_owner()) == '0000000000000000000000000000000000000000' + assert utils.remove_0x_head(market_maker.get_owner()) == '0000000000000000000000000000000000000000' -def test_initiate(market_maker, erc20, assert_tx_failed): +def test_initiate(t, chain, utils, market_maker, erc20, assert_tx_failed): erc20.approve(market_maker.address, 2*10**18) market_maker.initiate(erc20.address, 1*10**18, value=2*10**18) assert market_maker.get_total_eth_qty() == 2*10**18 assert market_maker.get_total_token_qty() == 1*10**18 assert market_maker.get_invariant() == 2*10**18 - assert u.remove_0x_head(market_maker.get_owner()) == t.a0.hex() - t.s = s + assert utils.remove_0x_head(market_maker.get_owner()) == t.a0.hex() + t.s = chain # Initiate cannot be called twice assert_tx_failed(lambda: market_maker.initiate(erc20.address, 1*10**18, value=2*10**18)) -def test_eth_to_tokens(market_maker, erc20): +def test_eth_to_tokens(t, market_maker, erc20): erc20.approve(market_maker.address, 2*10**18) market_maker.initiate(erc20.address, 1*10**18, value=2*10**18) assert erc20.balanceOf(market_maker.address) == 1000000000000000000 @@ -52,26 +50,26 @@ def test_eth_to_tokens(market_maker, erc20): assert market_maker.get_total_token_qty() == 0 -def test_tokens_to_eth(market_maker, erc20): +def test_tokens_to_eth(t, chain, market_maker, erc20): erc20.approve(market_maker.address, 2*10**18) market_maker.initiate(erc20.address, 1*10**18, value=2*10**18) - assert s.head_state.get_balance(market_maker.address) == 2000000000000000000 - assert s.head_state.get_balance(t.a1) == 999999999999999999999900 + assert chain.head_state.get_balance(market_maker.address) == 2000000000000000000 + assert chain.head_state.get_balance(t.a1) == 999999999999999999999900 erc20.approve(market_maker.address, 1*10**18, sender=t.k1) market_maker.tokens_to_eth(100, sender=t.k1) - assert s.head_state.get_balance(market_maker.address) == 1 - assert s.head_state.get_balance(t.a1) == 1000001999999999999999899 + assert chain.head_state.get_balance(market_maker.address) == 1 + assert chain.head_state.get_balance(t.a1) == 1000001999999999999999899 assert market_maker.get_total_eth_qty() == 1 -def test_owner_withdraw(market_maker, erc20, assert_tx_failed): +def test_owner_withdraw(t, chain, market_maker, erc20, assert_tx_failed): erc20.approve(market_maker.address, 2*10**18) market_maker.initiate(erc20.address, 1*10**18, value=2*10**18) - assert s.head_state.get_balance(t.a0) == 999992000000000000000000 + assert chain.head_state.get_balance(t.a0) == 999992000000000000000000 assert erc20.balanceOf(t.a0) == 20999999000000000000000000 - t.s = s + t.s = chain # Only owner can call owner_withdraw assert_tx_failed(lambda: market_maker.owner_withdraw(sender=t.k1)) market_maker.owner_withdraw() - assert s.head_state.get_balance(t.a0) == 999994000000000000000000 + assert chain.head_state.get_balance(t.a0) == 999994000000000000000000 assert erc20.balanceOf(t.a0) == 21000000000000000000000000 diff --git a/tests/examples/safe_remote_purchase/test_safe_remote_purchase.py b/tests/examples/safe_remote_purchase/test_safe_remote_purchase.py index 47d451e166..4278de6146 100644 --- a/tests/examples/safe_remote_purchase/test_safe_remote_purchase.py +++ b/tests/examples/safe_remote_purchase/test_safe_remote_purchase.py @@ -9,7 +9,6 @@ import pytest from ethereum.tools import tester from ethereum import utils -from tests.setup_transaction_tests import assert_tx_failed contract_code = open("examples/safe_remote_purchase/safe_remote_purchase.v.py").read() #Inital balance of accounts diff --git a/tests/examples/tokens/test_vipercoin.py b/tests/examples/tokens/test_vipercoin.py index e6cda17011..3fc8e94c6c 100644 --- a/tests/examples/tokens/test_vipercoin.py +++ b/tests/examples/tokens/test_vipercoin.py @@ -1,12 +1,8 @@ import pytest -from ethereum import utils from ethereum.abi import ValueOutOfBounds from ethereum.tools import tester -from tests.setup_transaction_tests import assert_tx_failed, get_last_log - - TOKEN_NAME = "Vipercoin" TOKEN_SYMBOL = "FANG" TOKEN_DECIMALS = 18 diff --git a/tests/examples/wallet/test_wallet.py b/tests/examples/wallet/test_wallet.py index a112496e8a..48abaf69d2 100644 --- a/tests/examples/wallet/test_wallet.py +++ b/tests/examples/wallet/test_wallet.py @@ -1,7 +1,5 @@ -import pytest from ethereum.tools import tester as t from ethereum import utils -from tests.setup_transaction_tests import assert_tx_failed t.s = t.Chain() t.s.head_state.gas_limit = 10**9 diff --git a/tests/parser/features/arithmetic/test_modulo.py b/tests/parser/features/arithmetic/test_modulo.py index eae1418f32..ed06447c4d 100644 --- a/tests/parser/features/arithmetic/test_modulo.py +++ b/tests/parser/features/arithmetic/test_modulo.py @@ -1,10 +1,6 @@ -import pytest from viper.exceptions import TypeMismatchException -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - -def test_modulo(): +def test_modulo(get_contract_with_gas_estimation): code = """ @public def num_modulo_num() -> num: @@ -30,7 +26,7 @@ def num_modulo_decimal() -> decimal: assert c.num_modulo_decimal() == .5 -def test_modulo_with_different_units(assert_compile_failed): +def test_modulo_with_different_units(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def foo(a: currency_value, b: num): @@ -39,7 +35,7 @@ def foo(a: currency_value, b: num): assert_compile_failed(lambda: get_contract_with_gas_estimation(code), TypeMismatchException) -def test_modulo_with_positional_input(assert_compile_failed): +def test_modulo_with_positional_input(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def foo(a: num(sec, positional), b: num): @@ -48,7 +44,7 @@ def foo(a: num(sec, positional), b: num): assert_compile_failed(lambda: get_contract_with_gas_estimation(code), TypeMismatchException) -def test_modulo_with_input_of_zero(assert_tx_failed): +def test_modulo_with_input_of_zero(assert_tx_failed, get_contract_with_gas_estimation): code = """ @public def foo(a: num, b: decimal) -> decimal: diff --git a/tests/parser/features/decorators/test_constant.py b/tests/parser/features/decorators/test_constant.py index 5a78efc51d..66923f9b9b 100644 --- a/tests/parser/features/decorators/test_constant.py +++ b/tests/parser/features/decorators/test_constant.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation_for_constants, get_contract - - -def test_constant_test(): +def test_constant_test(get_contract_with_gas_estimation_for_constants): constant_test = """ @public @constant diff --git a/tests/parser/features/decorators/test_private.py b/tests/parser/features/decorators/test_private.py index b7c05f30bf..0c1230acc3 100644 --- a/tests/parser/features/decorators/test_private.py +++ b/tests/parser/features/decorators/test_private.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_private_test(): +def test_private_test(get_contract_with_gas_estimation): private_test_code = """ @private def a() -> num: diff --git a/tests/parser/features/decorators/test_public.py b/tests/parser/features/decorators/test_public.py index 0037a4d658..a67f0c3ef0 100644 --- a/tests/parser/features/decorators/test_public.py +++ b/tests/parser/features/decorators/test_public.py @@ -1,11 +1,7 @@ -import pytest from viper.exceptions import StructureException -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - -def test_invalid_if_both_public_and_internal(assert_compile_failed): +def test_invalid_if_both_public_and_internal(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public @private @@ -16,7 +12,7 @@ def foo(): assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) -def test_invalid_if_visibility_isnt_declared(assert_compile_failed): +def test_invalid_if_visibility_isnt_declared(assert_compile_failed, get_contract_with_gas_estimation): code = """ def foo(): x = 1 diff --git a/tests/parser/features/external_contracts/test_erc20_abi.py b/tests/parser/features/external_contracts/test_erc20_abi.py index e78a0fe0ed..0ab98cd039 100644 --- a/tests/parser/features/external_contracts/test_erc20_abi.py +++ b/tests/parser/features/external_contracts/test_erc20_abi.py @@ -1,7 +1,5 @@ import pytest from ethereum.abi import ValueOutOfBounds -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract TOKEN_NAME = "Vipercoin" TOKEN_SYMBOL = "FANG" @@ -10,12 +8,12 @@ TOKEN_TOTAL_SUPPLY = TOKEN_INITIAL_SUPPLY * (10 ** TOKEN_DECIMALS) @pytest.fixture -def erc20(): +def erc20(get_contract): erc20_code = open('examples/tokens/vipercoin.v.py').read() return get_contract(erc20_code, args=[TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, TOKEN_INITIAL_SUPPLY]) @pytest.fixture -def erc20_caller(erc20): +def erc20_caller(erc20, get_contract): erc20_caller_code = """ token_address: address(ERC20) @@ -55,12 +53,12 @@ def pad_bytes32(instr): bstr = instr.encode() return bstr + (32 - len(bstr)) * b'\x00' -def test_initial_state(erc20_caller): +def test_initial_state(t, erc20_caller): assert erc20_caller.totalSupply() == TOKEN_TOTAL_SUPPLY == erc20_caller.balanceOf(t.a0) assert erc20_caller.balanceOf(t.a1) == 0 assert erc20_caller.symbol() == pad_bytes32(TOKEN_SYMBOL) -def test_call_transfer(erc20, erc20_caller, assert_tx_failed): +def test_call_transfer(t, chain, erc20, erc20_caller, assert_tx_failed): # Basic transfer. erc20.transfer(erc20_caller.address, 10) @@ -73,19 +71,19 @@ def test_call_transfer(erc20, erc20_caller, assert_tx_failed): # more than allowed assert_tx_failed(lambda: erc20_caller.transfer(t.a1, TOKEN_TOTAL_SUPPLY)) - t.s = s + t.s = chain # Negative transfer value. assert_tx_failed( function_to_test=lambda: erc20_caller.transfer(t.a1, -1), exception=ValueOutOfBounds ) -def test_caller_approve_allowance(erc20, erc20_caller): +def test_caller_approve_allowance(t, erc20, erc20_caller): assert erc20_caller.allowance(erc20.address, erc20_caller.address) == 0 assert erc20.approve(erc20_caller.address, 10) == True assert erc20_caller.allowance(t.a0, erc20_caller.address) == 10 -def test_caller_tranfer_from(erc20, erc20_caller, assert_tx_failed): +def test_caller_tranfer_from(t, erc20, erc20_caller, assert_tx_failed): # Cannot transfer tokens that are unavailable assert_tx_failed(lambda: erc20_caller.transferFrom(t.a0, erc20_caller.address, 10)) assert erc20.balanceOf(erc20_caller.address) == 0 diff --git a/tests/parser/features/external_contracts/test_external_contract_calls.py b/tests/parser/features/external_contracts/test_external_contract_calls.py index 65fbfde803..6e6cee7793 100644 --- a/tests/parser/features/external_contracts/test_external_contract_calls.py +++ b/tests/parser/features/external_contracts/test_external_contract_calls.py @@ -1,10 +1,7 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract from viper.exceptions import StructureException, VariableDeclarationException, InvalidTypeException -def test_external_contract_calls(): +def test_external_contract_calls(get_contract_with_gas_estimation): contract_1 = """ @public def foo(arg1: num) -> num: @@ -27,7 +24,7 @@ def bar(arg1: address, arg2: num) -> num: print('Successfully executed an external contract call') -def test_complicated_external_contract_calls(): +def test_complicated_external_contract_calls(get_contract_with_gas_estimation): contract_1 = """ lucky: public(num) @@ -62,7 +59,7 @@ def bar(arg1: address) -> num: print('Successfully executed a complicated external contract call') -def test_external_contract_calls_with_bytes(): +def test_external_contract_calls_with_bytes(get_contract_with_gas_estimation): contract_1 = """ @public def array() -> bytes <= 3: @@ -84,7 +81,7 @@ def get_array(arg1: address) -> bytes <= 3: assert c2.get_array(c.address) == b'dog' -def test_external_contract_call_state_change(): +def test_external_contract_call_state_change(get_contract): contract_1 = """ lucky: public(num) @@ -112,7 +109,7 @@ def set_lucky(arg1: address, arg2: num): print('Successfully executed an external contract call state change') -def test_constant_external_contract_call_cannot_change_state(assert_tx_failed): +def test_constant_external_contract_call_cannot_change_state(assert_tx_failed, get_contract_with_gas_estimation): contract_1 = """ lucky: public(num) @@ -146,7 +143,7 @@ def set_lucky_stmt(arg1: address, arg2: num) -> num: print('Successfully tested an constant external contract call attempted state change') -def test_external_contract_can_be_changed_based_on_address(): +def test_external_contract_can_be_changed_based_on_address(get_contract): contract_1 = """ lucky: public(num) @@ -168,7 +165,7 @@ def set_lucky(_lucky: num) -> num: """ lucky_number_2 = 3 - c2 = get_contract(contract_1) + c2 = get_contract(contract_2) contract_3 = """ class Foo(): @@ -187,7 +184,7 @@ def set_lucky(arg1: address, arg2: num): print('Successfully executed multiple external contract calls to different contracts based on address') -def test_external_contract_calls_with_public_globals(): +def test_external_contract_calls_with_public_globals(get_contract): contract_1 = """ lucky: public(num) @@ -213,7 +210,7 @@ def bar(arg1: address) -> num: print('Successfully executed an external contract call with public globals') -def test_external_contract_calls_with_multiple_contracts(): +def test_external_contract_calls_with_multiple_contracts(get_contract): contract_1 = """ lucky: public(num) @@ -253,7 +250,7 @@ def __init__(arg1: address): print('Successfully executed a multiple external contract calls') -def test_invalid_external_contract_call_to_the_same_contract(assert_tx_failed): +def test_invalid_external_contract_call_to_the_same_contract(assert_tx_failed, get_contract): contract_1 = """ @public def bar() -> num: @@ -286,7 +283,7 @@ def _expr(x: address) -> num: assert_tx_failed(lambda: c2._expr(c2.address)) -def test_invalid_nonexistent_contract_call(assert_tx_failed): +def test_invalid_nonexistent_contract_call(t, assert_tx_failed, get_contract): contract_1 = """ @public def bar() -> num: @@ -310,7 +307,7 @@ def foo(x: address) -> num: assert_tx_failed(lambda: c2.foo(t.a7)) -def test_invalid_contract_reference_declaration(assert_tx_failed): +def test_invalid_contract_reference_declaration(assert_tx_failed, get_contract): contract = """ class Bar(): get_magic_number: 1 @@ -324,7 +321,7 @@ def __init__(): assert_tx_failed(lambda: get_contract(contract), exception = StructureException) -def test_invalid_contract_reference_call(assert_tx_failed): +def test_invalid_contract_reference_call(assert_tx_failed, get_contract): contract = """ @public def bar(arg1: address, arg2: num) -> num: @@ -333,7 +330,7 @@ def bar(arg1: address, arg2: num) -> num: assert_tx_failed(lambda: get_contract(contract), exception = VariableDeclarationException) -def test_invalid_contract_reference_return_type(assert_tx_failed): +def test_invalid_contract_reference_return_type(assert_tx_failed, get_contract): contract = """ class Foo(): def foo(arg2: num) -> invalid: pass @@ -345,7 +342,7 @@ def bar(arg1: address, arg2: num) -> num: assert_tx_failed(lambda: get_contract(contract), exception = InvalidTypeException) -def test_external_contracts_must_be_declared_first_1(assert_tx_failed): +def test_external_contracts_must_be_declared_first_1(assert_tx_failed, get_contract): contract = """ item: public(num) @@ -356,7 +353,7 @@ def foo(arg2: num) -> num: pass assert_tx_failed(lambda: get_contract(contract), exception = StructureException) -def test_external_contracts_must_be_declared_first_2(assert_tx_failed): +def test_external_contracts_must_be_declared_first_2(assert_tx_failed, get_contract): contract = """ MyLog: __log__({}) @@ -367,7 +364,7 @@ def foo(arg2: num) -> num: pass assert_tx_failed(lambda: get_contract(contract), exception = StructureException) -def test_external_contracts_must_be_declared_first_3(assert_tx_failed): +def test_external_contracts_must_be_declared_first_3(assert_tx_failed, get_contract): contract = """ @public def foo() -> num: @@ -379,7 +376,7 @@ def foo(arg2: num) -> num: pass assert_tx_failed(lambda: get_contract(contract), exception = StructureException) -def test_external_contract_call_declaration_expr(): +def test_external_contract_call_declaration_expr(get_contract): contract_1 = """ @public def bar() -> num: @@ -403,7 +400,7 @@ def foo(contract_address: contract(Bar)) -> num: assert c2.foo(c1.address) == 1 -def test_external_contract_call_declaration_stmt(): +def test_external_contract_call_declaration_stmt(get_contract): contract_1 = """ lucky: num @@ -445,7 +442,7 @@ def get_lucky(contract_address: contract(Bar)) -> num: assert c1.get_lucky() == 1 assert c2.get_lucky(c1.address) == 1 -def test_complex_external_contract_call_declaration(): +def test_complex_external_contract_call_declaration(get_contract_with_gas_estimation): contract_1 = """ @public def get_lucky() -> num: @@ -485,7 +482,7 @@ def get_lucky() -> num: assert c3.get_lucky() == 2 -def test_address_can_returned_from_contract_type(t): +def test_address_can_returned_from_contract_type(get_contract, utils): contract_1 = """ @public def bar() -> num: @@ -508,12 +505,12 @@ def get_bar() -> num: c1 = get_contract(contract_1) c2 = get_contract(contract_2) c2.foo(c1.address) - assert u.remove_0x_head(c2.get_bar_contract()) == c1.address.hex() + assert utils.remove_0x_head(c2.get_bar_contract()) == c1.address.hex() assert c2.get_bar() == 1 -def test_invalid_external_contract_call_declaration_1(assert_compile_failed): +def test_invalid_external_contract_call_declaration_1(assert_compile_failed, get_contract): contract_1 = """ class Bar(): def bar() -> num: pass @@ -529,7 +526,7 @@ def foo(contract_address: contract(Boo)) -> num: assert_compile_failed(lambda: get_contract(contract_1), InvalidTypeException) -def test_invalid_external_contract_call_declaration_2(assert_compile_failed): +def test_invalid_external_contract_call_declaration_2(assert_compile_failed, get_contract): contract_1 = """ class Bar(): def bar() -> num: pass diff --git a/tests/parser/features/iteration/test_break.py b/tests/parser/features/iteration/test_break.py index e8968753fb..0f1d7a10ae 100644 --- a/tests/parser/features/iteration/test_break.py +++ b/tests/parser/features/iteration/test_break.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_break_test(): +def test_break_test(get_contract_with_gas_estimation): break_test = """ @public def log(n: num) -> num: @@ -25,7 +20,7 @@ def log(n: num) -> num: print('Passed for-loop break test') -def test_break_test_2(): +def test_break_test_2(get_contract_with_gas_estimation): break_test_2 = """ @public def log(n: num) -> num: @@ -53,7 +48,7 @@ def log(n: num) -> num: print('Passed for-loop break test 2') -def test_break_test_3(): +def test_break_test_3(get_contract_with_gas_estimation): break_test_3 = """ @public def log(n: num) -> num: diff --git a/tests/parser/features/iteration/test_for_in_list.py b/tests/parser/features/iteration/test_for_in_list.py index 67c9b06bd2..3a4ecc8ab7 100644 --- a/tests/parser/features/iteration/test_for_in_list.py +++ b/tests/parser/features/iteration/test_for_in_list.py @@ -1,10 +1,7 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract from viper.exceptions import StructureException, VariableDeclarationException -def test_basic_for_in_list(): +def test_basic_for_in_list(get_contract_with_gas_estimation): code = """ @public def data() -> num: @@ -20,7 +17,7 @@ def data() -> num: assert c.data() == 3 -def test_basic_for_list_liter(): +def test_basic_for_list_liter(get_contract_with_gas_estimation): code = """ @public def data() -> num: @@ -35,7 +32,7 @@ def data() -> num: assert c.data() == 7 -def test_basic_for_list_storage(): +def test_basic_for_list_storage(get_contract_with_gas_estimation): code = """ x: num[4] @@ -58,7 +55,7 @@ def data() -> num: assert c.data() == 7 -def test_basic_for_list_address(): +def test_basic_for_list_address(get_contract_with_gas_estimation): code = """ @public def data() -> address: @@ -80,7 +77,7 @@ def data() -> address: assert c.data() == "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" -def test_multiple_for_loops_1(): +def test_multiple_for_loops_1(get_contract_with_gas_estimation): code = """ @public def foo(x: num): @@ -92,7 +89,7 @@ def foo(x: num): """ get_contract_with_gas_estimation(code) -def test_multiple_for_loops_2(): +def test_multiple_for_loops_2(get_contract_with_gas_estimation): code = """ @public def foo(x: num): @@ -105,7 +102,7 @@ def foo(x: num): get_contract_with_gas_estimation(code) -def test_multiple_for_loops_3(): +def test_multiple_for_loops_3(get_contract_with_gas_estimation): code = """ @public def foo(x: num): @@ -118,7 +115,7 @@ def foo(x: num): get_contract_with_gas_estimation(code) -def test_multiple_loops_4(): +def test_multiple_loops_4(get_contract_with_gas_estimation): code = """ @public def foo(): @@ -130,7 +127,7 @@ def foo(): get_contract_with_gas_estimation(code) -def test_using_index_variable_after_loop(): +def test_using_index_variable_after_loop(get_contract_with_gas_estimation): code = """ @public def foo(): @@ -142,7 +139,7 @@ def foo(): get_contract_with_gas_estimation(code) -def test_basic_for_list_storage_address(): +def test_basic_for_list_storage_address(get_contract_with_gas_estimation): code = """ addresses: address[3] @@ -172,7 +169,7 @@ def iterate_return_second() -> address: assert c.ret(1) == c.iterate_return_second() == "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" -def test_basic_for_list_storage_decimal(): +def test_basic_for_list_storage_decimal(get_contract_with_gas_estimation): code = """ readings: decimal[3] @@ -204,7 +201,7 @@ def i_return(break_count: num) -> decimal: assert c.ret(0) == c.i_return(0) == 0.0001 -def test_altering_list_within_for_loop_1(assert_compile_failed): +def test_altering_list_within_for_loop_1(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def data() -> num: @@ -221,7 +218,7 @@ def data() -> num: assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) -def test_altering_list_within_for_loop_2(assert_compile_failed): +def test_altering_list_within_for_loop_2(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def foo(): @@ -233,7 +230,7 @@ def foo(): assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) -def test_altering_list_within_for_loop_storage(assert_compile_failed): +def test_altering_list_within_for_loop_storage(assert_compile_failed, get_contract_with_gas_estimation): code = """ s: num[6] @@ -255,7 +252,7 @@ def data() -> num: assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) -def test_invalid_nested_for_loop_1(assert_compile_failed): +def test_invalid_nested_for_loop_1(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def foo(x: num): @@ -266,7 +263,7 @@ def foo(x: num): assert_compile_failed(lambda: get_contract_with_gas_estimation(code),VariableDeclarationException) -def test_invalid_nested_for_loop_2(assert_compile_failed): +def test_invalid_nested_for_loop_2(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def foo(x: num): @@ -277,7 +274,7 @@ def foo(x: num): assert_compile_failed(lambda: get_contract_with_gas_estimation(code),VariableDeclarationException) -def test_invalid_iterator_assignment_1(assert_compile_failed): +def test_invalid_iterator_assignment_1(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def foo(x: num): @@ -287,7 +284,7 @@ def foo(x: num): assert_compile_failed(lambda: get_contract_with_gas_estimation(code), StructureException) -def test_invalid_iterator_assignment_2(assert_compile_failed): +def test_invalid_iterator_assignment_2(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def foo(x: num): diff --git a/tests/parser/features/iteration/test_range_in.py b/tests/parser/features/iteration/test_range_in.py index 5ef2727390..ced9c38709 100644 --- a/tests/parser/features/iteration/test_range_in.py +++ b/tests/parser/features/iteration/test_range_in.py @@ -1,10 +1,7 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract, assert_tx_failed from viper.exceptions import TypeMismatchException -def test_basic_in_list(): +def test_basic_in_list(get_contract_with_gas_estimation): code = """ @public def testin(x: num) -> bool: @@ -26,7 +23,7 @@ def testin(x: num) -> bool: assert c.testin(-1) is False -def test_in_storage_list(): +def test_in_storage_list(get_contract_with_gas_estimation): code = """ allowed: num[10] @@ -47,7 +44,7 @@ def in_test(x: num) -> bool: assert c.in_test(32000) is False -def test_cmp_in_list(): +def test_cmp_in_list(get_contract_with_gas_estimation): code = """ @public def in_test(x: num) -> bool: @@ -65,7 +62,7 @@ def in_test(x: num) -> bool: assert c.in_test(7) is True -def test_mixed_in_list(assert_compile_failed): +def test_mixed_in_list(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def testin() -> bool: @@ -77,7 +74,7 @@ def testin() -> bool: assert_compile_failed(lambda: get_contract_with_gas_estimation(code), TypeMismatchException) -def test_ownership(assert_tx_failed): +def test_ownership(t, assert_tx_failed, get_contract_with_gas_estimation): code = """ owners: address[2] diff --git a/tests/parser/features/iteration/test_repeater.py b/tests/parser/features/iteration/test_repeater.py index 05b779df84..554106ee16 100644 --- a/tests/parser/features/iteration/test_repeater.py +++ b/tests/parser/features/iteration/test_repeater.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_basic_repeater(): +def test_basic_repeater(get_contract_with_gas_estimation): basic_repeater = """ @public def repeat(z: num) -> num: @@ -17,7 +12,7 @@ def repeat(z: num) -> num: print('Passed basic repeater test') -def test_digit_reverser(): +def test_digit_reverser(get_contract_with_gas_estimation): digit_reverser = """ @public def reverse_digits(x: num) -> num: @@ -38,7 +33,7 @@ def reverse_digits(x: num) -> num: print('Passed digit reverser test') -def test_more_complex_repeater(): +def test_more_complex_repeater(get_contract_with_gas_estimation): more_complex_repeater = """ @public def repeat() -> num: @@ -54,7 +49,7 @@ def repeat() -> num: print('Passed complex repeater test') -def test_offset_repeater(): +def test_offset_repeater(get_contract_with_gas_estimation): offset_repeater = """ @public def sum() -> num: @@ -70,7 +65,7 @@ def sum() -> num: print('Passed repeater with offset test') -def test_offset_repeater_2(): +def test_offset_repeater_2(get_contract_with_gas_estimation): offset_repeater_2 = """ @public def sum(frm: num, to: num) -> num: diff --git a/tests/parser/features/test_assert.py b/tests/parser/features/test_assert.py index 1f6b762f77..b42fee13fa 100644 --- a/tests/parser/features/test_assert.py +++ b/tests/parser/features/test_assert.py @@ -1,8 +1,6 @@ import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract -def test_assert_refund(t): +def test_assert_refund(t, get_contract_with_gas_estimation): code = """ @public def foo(): diff --git a/tests/parser/features/test_assignment.py b/tests/parser/features/test_assignment.py index 12b078417b..814210681b 100644 --- a/tests/parser/features/test_assignment.py +++ b/tests/parser/features/test_assignment.py @@ -1,10 +1,7 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract from viper.exceptions import ConstancyViolationException -def test_augassign(): +def test_augassign(get_contract_with_gas_estimation): augassign_test = """ @public def augadd(x: num, y: num) -> num: @@ -47,7 +44,7 @@ def augmod(x: num, y: num) -> num: print('Passed aug-assignment test') -def test_invalid_assign(assert_compile_failed): +def test_invalid_assign(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def foo(x:num): @@ -56,7 +53,7 @@ def foo(x:num): assert_compile_failed(lambda: get_contract_with_gas_estimation(code), ConstancyViolationException) -def test_invalid_augassign(assert_compile_failed): +def test_invalid_augassign(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def foo(x:num): diff --git a/tests/parser/features/test_bytes_map_keys.py b/tests/parser/features/test_bytes_map_keys.py index 22a5f1509d..01ee0aa7ab 100644 --- a/tests/parser/features/test_bytes_map_keys.py +++ b/tests/parser/features/test_bytes_map_keys.py @@ -1,10 +1,8 @@ import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract from viper.exceptions import TypeMismatchException -def test_basic_bytes_keys(): +def test_basic_bytes_keys(get_contract): code = """ mapped_bytes: num[bytes <= 5] @@ -24,7 +22,7 @@ def get(k: bytes <= 5) -> num: assert c.get("test") == 54321 -def test_basic_bytes_literal_key(): +def test_basic_bytes_literal_key(get_contract): code = """ mapped_bytes: num[bytes <= 5] @@ -44,7 +42,7 @@ def get(k: bytes <= 5) -> num: assert c.get("test") == 54321 -def test_basic_long_bytes_as_keys(): +def test_basic_long_bytes_as_keys(get_contract): code = """ mapped_bytes: num[bytes <= 34] @@ -64,7 +62,7 @@ def get(k: bytes <= 34) -> num: assert c.get("a" * 34) == 6789 -def test_basic_very_long_bytes_as_keys(): +def test_basic_very_long_bytes_as_keys(get_contract): code = """ mapped_bytes: num[bytes <= 4096] @@ -84,7 +82,7 @@ def get(k: bytes <= 4096) -> num: assert c.get("test" * 1024) == 6789 -def test_mismatched_byte_length(): +def test_mismatched_byte_length(get_contract): code = """ mapped_bytes: num[bytes <= 34] diff --git a/tests/parser/features/test_clampers.py b/tests/parser/features/test_clampers.py index ced2e6a98b..3136db1180 100644 --- a/tests/parser/features/test_clampers.py +++ b/tests/parser/features/test_clampers.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_clamper_test_code(): +def test_clamper_test_code(t, get_contract_with_gas_estimation): clamper_test_code = """ @public def foo(s: bytes <= 3) -> bytes <= 3: diff --git a/tests/parser/features/test_comments.py b/tests/parser/features/test_comments.py index 045a019f9f..9da2461ceb 100644 --- a/tests/parser/features/test_comments.py +++ b/tests/parser/features/test_comments.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_comment_test(): +def test_comment_test(get_contract_with_gas_estimation): comment_test = """ @public def foo() -> num: diff --git a/tests/parser/features/test_conditionals.py b/tests/parser/features/test_conditionals.py index 82bb8728b1..a2651dfa9e 100644 --- a/tests/parser/features/test_conditionals.py +++ b/tests/parser/features/test_conditionals.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_conditional_return_code(): +def test_conditional_return_code(get_contract_with_gas_estimation): conditional_return_code = """ @public def foo(i: bool) -> num: diff --git a/tests/parser/features/test_constructor.py b/tests/parser/features/test_constructor.py index 8d05ee79c6..751dea8d50 100644 --- a/tests/parser/features/test_constructor.py +++ b/tests/parser/features/test_constructor.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_init_argument_test(): +def test_init_argument_test(get_contract_with_gas_estimation): init_argument_test = """ moose: num @@ -21,7 +16,7 @@ def returnMoose() -> num: print('Passed init argument test') -def test_constructor_advanced_code(): +def test_constructor_advanced_code(get_contract_with_gas_estimation): constructor_advanced_code = """ twox: num @@ -37,7 +32,7 @@ def get_twox() -> num: assert c.get_twox() == 10 -def test_constructor_advanced_code2(): +def test_constructor_advanced_code2(get_contract_with_gas_estimation): constructor_advanced_code2 = """ comb: num @@ -54,7 +49,7 @@ def get_comb() -> num: print("Passed advanced init argument tests") -def test_large_input_code(): +def test_large_input_code(get_contract_with_gas_estimation): large_input_code = """ @public def foo(x: num) -> num: @@ -72,7 +67,7 @@ def foo(x: num) -> num: assert not success -def test_large_input_code_2(): +def test_large_input_code_2(t, get_contract_with_gas_estimation): large_input_code_2 = """ @public def __init__(x: num): diff --git a/tests/parser/features/test_gas.py b/tests/parser/features/test_gas.py index e2ea293ce7..77300f714d 100644 --- a/tests/parser/features/test_gas.py +++ b/tests/parser/features/test_gas.py @@ -1,12 +1,8 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - from viper.parser.parser import parse_to_lll from viper.parser import parser_utils -def test_gas_call(): +def test_gas_call(get_contract_with_gas_estimation): gas_call = """ @public def foo() -> num: diff --git a/tests/parser/features/test_internal_call.py b/tests/parser/features/test_internal_call.py index 8d58ec63c3..5b00edaf6c 100644 --- a/tests/parser/features/test_internal_call.py +++ b/tests/parser/features/test_internal_call.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_selfcall_code(): +def test_selfcall_code(get_contract_with_gas_estimation): selfcall_code = """ @public def foo() -> num: @@ -20,7 +15,7 @@ def bar() -> num: print("Passed no-argument self-call test") -def test_selfcall_code_2(): +def test_selfcall_code_2(get_contract_with_gas_estimation, utils): selfcall_code_2 = """ @public def double(x: num) -> num: @@ -41,12 +36,12 @@ def return_hash_of_rzpadded_cow() -> bytes32: c = get_contract_with_gas_estimation(selfcall_code_2) assert c.returnten() == 10 - assert c.return_hash_of_rzpadded_cow() == u.sha3(b'cow' + b'\x00' * 29) + assert c.return_hash_of_rzpadded_cow() == utils.sha3(b'cow' + b'\x00' * 29) print("Passed single fixed-size argument self-call test") -def test_selfcall_code_3(): +def test_selfcall_code_3(get_contract_with_gas_estimation, utils): selfcall_code_3 = """ @public def _hashy2(x: bytes <= 100) -> bytes32: @@ -66,13 +61,13 @@ def returnten() -> num: """ c = get_contract_with_gas_estimation(selfcall_code_3) - assert c.return_hash_of_cow_x_30() == u.sha3(b'cow' * 30) + assert c.return_hash_of_cow_x_30() == utils.sha3(b'cow' * 30) assert c.returnten() == 10 print("Passed single variable-size argument self-call test") -def test_selfcall_code_4(): +def test_selfcall_code_4(get_contract_with_gas_estimation): selfcall_code_4 = """ @public def summy(x: num, y: num) -> num: @@ -116,7 +111,7 @@ def return_goose2() -> bytes <= 10: print("Passed multi-argument self-call test") -def test_selfcall_code_5(): +def test_selfcall_code_5(get_contract_with_gas_estimation): selfcall_code_5 = """ counter: num @@ -136,7 +131,7 @@ def returnten() -> num: print("Passed self-call statement test") -def test_selfcall_code_6(): +def test_selfcall_code_6(get_contract_with_gas_estimation): selfcall_code_6 = """ excls: bytes <= 32 diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index 135e753e9b..c301b9677e 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -1,10 +1,7 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract, assert_tx_failed, get_last_log from viper.exceptions import VariableDeclarationException, TypeMismatchException, StructureException -def test_empy_event_logging(): +def test_empy_event_logging(get_contract_with_gas_estimation, utils, chain): loggy_code = """ MyLog: __log__({}) @@ -15,8 +12,8 @@ def foo(): c = get_contract_with_gas_estimation(loggy_code) c.foo() - logs = s.head_state.receipts[-1].logs[-1] - event_id = u.bytes_to_int(u.sha3(bytes('MyLog()', 'utf-8'))) + logs = chain.head_state.receipts[-1].logs[-1] + event_id = utils.bytes_to_int(utils.sha3(bytes('MyLog()', 'utf-8'))) # Event id is always the first topic assert logs.topics[0] == event_id # Event id is calculated correctly @@ -27,7 +24,7 @@ def foo(): assert c.translator.decode_event(logs.topics, logs.data) == {'_event_type': b'MyLog'} -def test_event_logging_with_topics(): +def test_event_logging_with_topics(get_contract_with_gas_estimation, chain, utils): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3)}) @@ -38,8 +35,8 @@ def foo(): c = get_contract_with_gas_estimation(loggy_code) c.foo() - logs = s.head_state.receipts[-1].logs[-1] - event_id = u.bytes_to_int(u.sha3(bytes('MyLog(bytes3)', 'utf-8'))) + logs = chain.head_state.receipts[-1].logs[-1] + event_id = utils.bytes_to_int(utils.sha3(bytes('MyLog(bytes3)', 'utf-8'))) # Event id is always the first topic assert logs.topics[0] == event_id # Event id is calculated correctly @@ -50,7 +47,7 @@ def foo(): assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': b'bar', '_event_type': b'MyLog'} -def test_event_logging_with_multiple_topics(): +def test_event_logging_with_multiple_topics(get_contract_with_gas_estimation, chain, utils): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3), arg2: indexed(bytes <= 4), arg3: indexed(address)}) @@ -61,8 +58,8 @@ def foo(): c = get_contract_with_gas_estimation(loggy_code) c.foo() - logs = s.head_state.receipts[-1].logs[-1] - event_id = u.bytes_to_int(u.sha3(bytes('MyLog(bytes3,bytes4,address)', 'utf-8'))) + logs = chain.head_state.receipts[-1].logs[-1] + event_id = utils.bytes_to_int(utils.sha3(bytes('MyLog(bytes3,bytes4,address)', 'utf-8'))) # Event id is always the first topic assert logs.topics[0] == event_id # Event id is calculated correctly @@ -73,7 +70,7 @@ def foo(): assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': b'bar', 'arg2': b'home', 'arg3': '0x'+c.address.hex(), '_event_type': b'MyLog'} -def test_logging_the_same_event_multiple_times_with_topics(): +def test_logging_the_same_event_multiple_times_with_topics(get_contract_with_gas_estimation, chain, utils): loggy_code = """ MyLog: __log__({arg1: indexed(num), arg2: indexed(address)}) @@ -91,8 +88,8 @@ def bar(): c = get_contract_with_gas_estimation(loggy_code) c.foo() c.bar() - logs = s.head_state.receipts[-1].logs[-1] - event_id = u.bytes_to_int(u.sha3(bytes('MyLog(int128,address)', 'utf-8'))) + logs = chain.head_state.receipts[-1].logs[-1] + event_id = utils.bytes_to_int(utils.sha3(bytes('MyLog(int128,address)', 'utf-8'))) # Event id is always the first topic assert logs.topics[0] == event_id # Event id is calculated correctly @@ -103,7 +100,7 @@ def bar(): assert c.translator.decode_event(logs.topics, logs.data) == {'_event_type': b'MyLog', 'arg1': 1, 'arg2': '0x' + c.address.hex()} -def test_event_logging_cannot_have_more_than_three_topics(assert_tx_failed): +def test_event_logging_cannot_have_more_than_three_topics(assert_tx_failed, get_contract_with_gas_estimation): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3), arg2: indexed(bytes <= 4), arg3: indexed(address), arg4: indexed(num)}) """ @@ -111,7 +108,7 @@ def test_event_logging_cannot_have_more_than_three_topics(assert_tx_failed): assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) -def test_event_logging_with_data(): +def test_event_logging_with_data(get_contract_with_gas_estimation, chain, utils): loggy_code = """ MyLog: __log__({arg1: num}) @@ -122,8 +119,8 @@ def foo(): c = get_contract_with_gas_estimation(loggy_code) c.foo() - logs = s.head_state.receipts[-1].logs[-1] - event_id = u.bytes_to_int(u.sha3(bytes('MyLog(int128)', 'utf-8'))) + logs = chain.head_state.receipts[-1].logs[-1] + event_id = utils.bytes_to_int(utils.sha3(bytes('MyLog(int128)', 'utf-8'))) # Event id is always the first topic assert logs.topics[0] == event_id # Event id is calculated correctly @@ -134,7 +131,7 @@ def foo(): assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': 123, '_event_type': b'MyLog'} -def test_event_loggging_with_fixed_array_data(): +def test_event_loggging_with_fixed_array_data(get_contract_with_gas_estimation, chain, utils): loggy_code = """ MyLog: __log__({arg1: num[2], arg2: timestamp[3], arg3: num[2][2]}) @@ -146,8 +143,8 @@ def foo(): c = get_contract_with_gas_estimation(loggy_code) c.foo() - logs = s.head_state.receipts[-1].logs[-1] - event_id = u.bytes_to_int(u.sha3(bytes('MyLog(int128[2],int128[3],int128[2][2])', 'utf-8'))) + logs = chain.head_state.receipts[-1].logs[-1] + event_id = utils.bytes_to_int(utils.sha3(bytes('MyLog(int128[2],int128[3],int128[2][2])', 'utf-8'))) # # Event id is always the first topic assert logs.topics[0] == event_id # # Event id is calculated correctly @@ -155,11 +152,11 @@ def foo(): # # Event abi is created correctly assert c.translator.event_data[event_id] == {'types': ['int128[2]', 'int128[3]', 'int128[2][2]'], 'name': 'MyLog', 'names': ['arg1', 'arg2', 'arg3'], 'indexed': [False, False, False], 'anonymous': False} # # Event is decoded correctly - timestamp = s.head_state.timestamp + timestamp = chain.head_state.timestamp assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': [1, 2], 'arg2': [timestamp, timestamp + 1, timestamp + 2], 'arg3': [[1, 2], [1, 2]], '_event_type': b'MyLog'} -def test_logging_with_input_bytes_1(bytes_helper): +def test_logging_with_input_bytes_1(bytes_helper, get_contract_with_gas_estimation, chain, utils): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 4), arg2: indexed(bytes <= 29), arg3: bytes<=31}) @@ -170,8 +167,8 @@ def foo(arg1: bytes <= 29, arg2: bytes <= 31): c = get_contract_with_gas_estimation(loggy_code) c.foo('bar', 'foo') - logs = s.head_state.receipts[-1].logs[-1] - event_id = u.bytes_to_int(u.sha3(bytes('MyLog(bytes4,bytes29,bytes31)', 'utf-8'))) + logs = chain.head_state.receipts[-1].logs[-1] + event_id = utils.bytes_to_int(utils.sha3(bytes('MyLog(bytes4,bytes29,bytes31)', 'utf-8'))) # # Event id is always the first topic assert logs.topics[0] == event_id # # Event id is calculated correctly @@ -182,7 +179,7 @@ def foo(arg1: bytes <= 29, arg2: bytes <= 31): assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': b'bar\x00', 'arg2': bytes_helper('bar', 29), 'arg3': bytes_helper('foo', 31), '_event_type': b'MyLog'} -def test_event_logging_with_bytes_input_2(t, bytes_helper): +def test_event_logging_with_bytes_input_2(t, bytes_helper, get_contract_with_gas_estimation, chain, utils): loggy_code = """ MyLog: __log__({arg1: bytes <= 20}) @@ -193,8 +190,8 @@ def foo(_arg1: bytes <= 20): c = get_contract_with_gas_estimation(loggy_code) c.foo('hello') - logs = s.head_state.receipts[-1].logs[-1] - event_id = u.bytes_to_int(u.sha3(bytes('MyLog(bytes20)', 'utf-8'))) + logs = chain.head_state.receipts[-1].logs[-1] + event_id = utils.bytes_to_int(utils.sha3(bytes('MyLog(bytes20)', 'utf-8'))) # Event id is always the first topic assert logs.topics[0] == event_id # Event id is calculated correctly @@ -205,7 +202,7 @@ def foo(_arg1: bytes <= 20): assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': bytes_helper('hello', 20), '_event_type': b'MyLog'} -def test_event_logging_with_bytes_input_3(bytes_helper): +def test_event_logging_with_bytes_input_3(get_contract, chain, utils): loggy_code = """ MyLog: __log__({arg1: bytes <= 5}) @@ -216,8 +213,8 @@ def foo(_arg1: bytes <= 5): c = get_contract(loggy_code) c.foo('hello') - logs = s.head_state.receipts[-1].logs[-1] - event_id = u.bytes_to_int(u.sha3(bytes('MyLog(bytes5)', 'utf-8'))) + logs = chain.head_state.receipts[-1].logs[-1] + event_id = utils.bytes_to_int(utils.sha3(bytes('MyLog(bytes5)', 'utf-8'))) # Event id is always the first topic assert logs.topics[0] == event_id # Event id is calculated correctly @@ -228,7 +225,7 @@ def foo(_arg1: bytes <= 5): assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': b'hello', '_event_type': b'MyLog'} -def test_event_logging_with_data_with_different_types(): +def test_event_logging_with_data_with_different_types(get_contract_with_gas_estimation, chain, utils): loggy_code = """ MyLog: __log__({arg1: num, arg2: bytes <= 4, arg3: bytes <= 3, arg4: address, arg5: address, arg6: timestamp}) @@ -239,8 +236,8 @@ def foo(): c = get_contract_with_gas_estimation(loggy_code) c.foo() - logs = s.head_state.receipts[-1].logs[-1] - event_id = u.bytes_to_int(u.sha3(bytes('MyLog(int128,bytes4,bytes3,address,address,int128)', 'utf-8'))) + logs = chain.head_state.receipts[-1].logs[-1] + event_id = utils.bytes_to_int(utils.sha3(bytes('MyLog(int128,bytes4,bytes3,address,address,int128)', 'utf-8'))) # Event id is always the first topic assert logs.topics[0] == event_id # Event id is calculated correctly @@ -248,10 +245,10 @@ def foo(): # Event abi is created correctly assert c.translator.event_data[event_id] == {'types': ['int128', 'bytes4', 'bytes3', 'address', 'address', 'int128'], 'name': 'MyLog', 'names': ['arg1', 'arg2', 'arg3', 'arg4', 'arg5', 'arg6'], 'indexed': [False, False, False, False, False, False], 'anonymous': False} # Event is decoded correctly - assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': 123, 'arg2': b'home', 'arg3': b'bar', 'arg4': '0xc305c901078781c232a2a521c2af7980f8385ee9', 'arg5': '0x' + c.address.hex(), 'arg6': s.head_state.timestamp, '_event_type': b'MyLog'} + assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': 123, 'arg2': b'home', 'arg3': b'bar', 'arg4': '0xc305c901078781c232a2a521c2af7980f8385ee9', 'arg5': '0x' + c.address.hex(), 'arg6': chain.head_state.timestamp, '_event_type': b'MyLog'} -def test_event_logging_with_topics_and_data_1(): +def test_event_logging_with_topics_and_data_1(get_contract_with_gas_estimation, chain, utils): loggy_code = """ MyLog: __log__({arg1: indexed(num), arg2: bytes <= 3}) @@ -262,8 +259,8 @@ def foo(): c = get_contract_with_gas_estimation(loggy_code) c.foo() - logs = s.head_state.receipts[-1].logs[-1] - event_id = u.bytes_to_int(u.sha3(bytes('MyLog(int128,bytes3)', 'utf-8'))) + logs = chain.head_state.receipts[-1].logs[-1] + event_id = utils.bytes_to_int(utils.sha3(bytes('MyLog(int128,bytes3)', 'utf-8'))) # Event id is always the first topic assert logs.topics[0] == event_id # Event id is calculated correctly @@ -274,7 +271,7 @@ def foo(): assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': 1, 'arg2': b'bar', '_event_type': b'MyLog'} -def test_event_logging_with_multiple_logs_topics_and_data(): +def test_event_logging_with_multiple_logs_topics_and_data(get_contract_with_gas_estimation, chain, utils): loggy_code = """ MyLog: __log__({arg1: indexed(num), arg2: bytes <= 3}) YourLog: __log__({arg1: indexed(address), arg2: bytes <= 5}) @@ -287,10 +284,10 @@ def foo(): c = get_contract_with_gas_estimation(loggy_code) c.foo() - logs1 = s.head_state.receipts[-1].logs[-2] - logs2 = s.head_state.receipts[-1].logs[-1] - event_id1 = u.bytes_to_int(u.sha3(bytes('MyLog(int128,bytes3)', 'utf-8'))) - event_id2 = u.bytes_to_int(u.sha3(bytes('YourLog(address,bytes5)', 'utf-8'))) + logs1 = chain.head_state.receipts[-1].logs[-2] + logs2 = chain.head_state.receipts[-1].logs[-1] + event_id1 = utils.bytes_to_int(utils.sha3(bytes('MyLog(int128,bytes3)', 'utf-8'))) + event_id2 = utils.bytes_to_int(utils.sha3(bytes('YourLog(address,bytes5)', 'utf-8'))) # Event id is always the first topic assert logs1.topics[0] == event_id1 assert logs2.topics[0] == event_id2 @@ -305,7 +302,7 @@ def foo(): assert c.translator.decode_event(logs2.topics, logs2.data) == {'arg1': '0x' + c.address.hex(), 'arg2': b'house', '_event_type': b'YourLog'} -def test_fails_when_input_is_the_wrong_type(assert_tx_failed): +def test_fails_when_input_is_the_wrong_type(t, assert_tx_failed, get_contract_with_gas_estimation, chain): loggy_code = """ MyLog: __log__({arg1: indexed(num)}) @@ -313,11 +310,11 @@ def test_fails_when_input_is_the_wrong_type(assert_tx_failed): def foo_(): log.MyLog('yo') """ - t.s = s + t.s = chain assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) -def test_fails_when_topic_is_the_wrong_size(assert_tx_failed): +def test_fails_when_topic_is_the_wrong_size(t, assert_tx_failed, get_contract_with_gas_estimation, chain): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3)}) @@ -325,11 +322,11 @@ def test_fails_when_topic_is_the_wrong_size(assert_tx_failed): def foo(): log.MyLog('bars') """ - t.s = s + t.s = chain assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) -def test_fails_when_input_topic_is_the_wrong_size(assert_tx_failed): +def test_fails_when_input_topic_is_the_wrong_size(t, assert_tx_failed, get_contract_with_gas_estimation, chain): loggy_code = """ MyLog: __log__({arg1: indexed(bytes <= 3)}) @@ -337,11 +334,11 @@ def test_fails_when_input_topic_is_the_wrong_size(assert_tx_failed): def foo(arg1: bytes <= 4): log.MyLog(arg1) """ - t.s = s + t.s = chain assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) -def test_fails_when_data_is_the_wrong_size(assert_tx_failed): +def test_fails_when_data_is_the_wrong_size(t, assert_tx_failed, get_contract_with_gas_estimation, chain): loggy_code = """ MyLog: __log__({arg1: bytes <= 3}) @@ -349,11 +346,11 @@ def test_fails_when_data_is_the_wrong_size(assert_tx_failed): def foo(): log.MyLog('bars') """ - t.s = s + t.s = chain assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) -def test_fails_when_input_data_is_the_wrong_size(assert_tx_failed): +def test_fails_when_input_data_is_the_wrong_size(t, assert_tx_failed, get_contract_with_gas_estimation, chain): loggy_code = """ MyLog: __log__({arg1: bytes <= 3}) @@ -361,11 +358,11 @@ def test_fails_when_input_data_is_the_wrong_size(assert_tx_failed): def foo(arg1: bytes <= 4): log.MyLog(arg1) """ - t.s = s + t.s = chain assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) -def test_fails_when_log_data_is_over_32_bytes(assert_tx_failed): +def test_fails_when_log_data_is_over_32_bytes(t, assert_tx_failed, get_contract_with_gas_estimation, chain): loggy_code = """ MyLog: __log__({arg1: bytes <= 100}) @@ -373,22 +370,22 @@ def test_fails_when_log_data_is_over_32_bytes(assert_tx_failed): def foo(): pass """ - t.s = s + t.s = chain assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) -def test_logging_fails_with_over_three_topics(assert_tx_failed): +def test_logging_fails_with_over_three_topics(t, assert_tx_failed, get_contract_with_gas_estimation, chain): loggy_code = """ MyLog: __log__({arg1: indexed(num), arg2: indexed(num), arg3: indexed(num), arg4: indexed(num)}) @public def __init__(): log.MyLog(1, 2, 3, 4) """ - t.s = s + t.s = chain assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) -def test_logging_fails_with_duplicate_log_names(assert_tx_failed): +def test_logging_fails_with_duplicate_log_names(t, assert_tx_failed, get_contract_with_gas_estimation, chain): loggy_code = """ MyLog: __log__({}) MyLog: __log__({}) @@ -397,22 +394,22 @@ def test_logging_fails_with_duplicate_log_names(assert_tx_failed): def foo(): log.MyLog() """ - t.s = s + t.s = chain assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) -def test_logging_fails_with_when_log_is_undeclared(assert_tx_failed): +def test_logging_fails_with_when_log_is_undeclared(t, assert_tx_failed, get_contract_with_gas_estimation, chain): loggy_code = """ @public def foo(): log.MyLog() """ - t.s = s + t.s = chain assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) -def test_logging_fails_with_topic_type_mismatch(assert_tx_failed): +def test_logging_fails_with_topic_type_mismatch(t, assert_tx_failed, get_contract_with_gas_estimation, chain): loggy_code = """ MyLog: __log__({arg1: indexed(num)}) @@ -420,11 +417,11 @@ def test_logging_fails_with_topic_type_mismatch(assert_tx_failed): def foo(): log.MyLog(self) """ - t.s = s + t.s = chain assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), TypeMismatchException) -def test_logging_fails_with_data_type_mismatch(assert_tx_failed): +def test_logging_fails_with_data_type_mismatch(t, assert_tx_failed, get_contract_with_gas_estimation, chain): loggy_code = """ MyLog: __log__({arg1: bytes <= 3}) @@ -432,20 +429,20 @@ def test_logging_fails_with_data_type_mismatch(assert_tx_failed): def foo(): log.MyLog(self) """ - t.s = s + t.s = chain assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), AttributeError) -def test_logging_fails_after_a_global_declaration(assert_tx_failed): +def test_logging_fails_after_a_global_declaration(t, assert_tx_failed, get_contract_with_gas_estimation, chain): loggy_code = """ age: num MyLog: __log__({arg1: bytes <= 3}) """ - t.s = s + t.s = chain assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), StructureException) -def test_logging_fails_after_a_function_declaration(assert_tx_failed): +def test_logging_fails_after_a_function_declaration(t, assert_tx_failed, get_contract_with_gas_estimation): loggy_code = """ @public def foo(): @@ -456,7 +453,7 @@ def foo(): assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), StructureException) -def test_logging_fails_when_number_of_arguments_is_greater_than_declaration(assert_tx_failed): +def test_logging_fails_when_number_of_arguments_is_greater_than_declaration(t, assert_tx_failed, get_contract_with_gas_estimation): loggy_code = """ MyLog: __log__({arg1: num}) @@ -467,7 +464,7 @@ def foo(): assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) -def test_logging_fails_when_number_of_arguments_is_less_than_declaration(assert_tx_failed): +def test_logging_fails_when_number_of_arguments_is_less_than_declaration(assert_tx_failed, get_contract_with_gas_estimation): loggy_code = """ MyLog: __log__({arg1: num, arg2: num}) @@ -478,7 +475,7 @@ def foo(): assert_tx_failed(lambda: get_contract_with_gas_estimation(loggy_code), VariableDeclarationException) -def test_loggy_code(): +def test_loggy_code(get_contract_with_gas_estimation, chain): loggy_code = """ s: bytes <= 100 @@ -502,18 +499,19 @@ def ioo(inp: bytes <= 100): c = get_contract_with_gas_estimation(loggy_code) c.foo() - assert s.head_state.receipts[-1].logs[0].data == b'moo' + assert chain.head_state.receipts[-1].logs[0].data == b'moo' c.goo() - assert s.head_state.receipts[-1].logs[0].data == b'moo2' - assert s.head_state.receipts[-1].logs[0].topics == [0x1234567812345678123456781234567812345678123456781234567812345678] + assert chain.head_state.receipts[-1].logs[0].data == b'moo2' + assert chain.head_state.receipts[-1].logs[0].topics == [0x1234567812345678123456781234567812345678123456781234567812345678] c.hoo() - assert s.head_state.receipts[-1].logs[0].data == b'moo3' + assert chain.head_state.receipts[-1].logs[0].data == b'moo3' c.ioo(b"moo4") - assert s.head_state.receipts[-1].logs[0].data == b'moo4' + assert chain.head_state.receipts[-1].logs[0].data == b'moo4' print("Passed raw log tests") -def test_variable_list_packing(get_last_log): +def test_variable_list_packing(t, get_last_log, get_contract_with_gas_estimation, chain): + t.s = chain code = """ Bar: __log__({_value: num[4]}) @@ -528,7 +526,8 @@ def foo(): assert get_last_log(t, c)["_value"] == [1, 2, 3, 4] -def test_literal_list_packing(get_last_log): +def test_literal_list_packing(t, get_last_log, get_contract_with_gas_estimation, chain): + t.s = chain code = """ Bar: __log__({_value: num[4]}) @@ -542,7 +541,8 @@ def foo(): assert get_last_log(t, c)["_value"] == [1, 2, 3, 4] -def test_passed_list_packing(get_last_log): +def test_passed_list_packing(t, get_last_log, get_contract_with_gas_estimation, chain): + t.s = chain code = """ Bar: __log__({_value: num[4]}) @@ -556,7 +556,9 @@ def foo(barbaric: num[4]): assert get_last_log(t, c)["_value"] == [4, 5, 6, 7] -def test_variable_decimal_list_packing(get_last_log): +def test_variable_decimal_list_packing(t, get_last_log, get_contract_with_gas_estimation, chain): + t.s = chain + code = """ Bar: __log__({_value: decimal[4]}) diff --git a/tests/parser/features/test_packing.py b/tests/parser/features/test_packing.py index a73d28309b..9623904e82 100644 --- a/tests/parser/features/test_packing.py +++ b/tests/parser/features/test_packing.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_packing_test(): +def test_packing_test(get_contract_with_gas_estimation): packing_test = """ x: num y: num[5] diff --git a/tests/parser/functions/rlp/conftest.py b/tests/parser/functions/rlp/conftest.py new file mode 100644 index 0000000000..8c7b819b66 --- /dev/null +++ b/tests/parser/functions/rlp/conftest.py @@ -0,0 +1,28 @@ +import pytest +import rlp +from viper import utils as viper_utils +from ethereum import transactions, messages + +@pytest.fixture +def inject_tx(utils, chain): + def inject_tx(txhex): + tx = rlp.decode(utils.decode_hex(txhex[2:]), transactions.Transaction) + chain.head_state.set_balance(tx.sender, tx.startgas * tx.gasprice) + chain.chain.state.set_balance(tx.sender, tx.startgas * tx.gasprice) + messages.apply_transaction(chain.head_state, tx) + chain.block.transactions.append(tx) + contract_address = utils.sha3(rlp.encode([tx.sender, 0]))[12:] + assert chain.head_state.get_code(contract_address) + chain.mine(1) + chain.head_state.gas_limit = 10**9 + return contract_address + return inject_tx + +@pytest.fixture +def fake_tx(inject_tx): + def fake_tx(): + tx = "0xf9035b808506fc23ac0083045ef88080b903486103305660006109ac5260006109cc527f0100000000000000000000000000000000000000000000000000000000000000600035046109ec526000610a0c5260006109005260c06109ec51101515585760f86109ec51101561006e5760bf6109ec510336141558576001610a0c52610098565b60013560f76109ec51036020035260005160f66109ec510301361415585760f66109ec5103610a0c525b61010060016064818352015b36610a0c511015156100b557610291565b7f0100000000000000000000000000000000000000000000000000000000000000610a0c5135046109ec526109cc5160206109ac51026040015260016109ac51016109ac5260806109ec51101561013b5760016109cc5161044001526001610a0c516109cc5161046001376001610a0c5101610a0c5260216109cc51016109cc52610281565b60b86109ec5110156101d15760806109ec51036109cc51610440015260806109ec51036001610a0c51016109cc51610460013760816109ec5114156101ac5760807f01000000000000000000000000000000000000000000000000000000000000006001610a0c5101350410151558575b607f6109ec5103610a0c5101610a0c5260606109ec51036109cc51016109cc52610280565b60c06109ec51101561027d576001610a0c51013560b76109ec510360200352600051610a2c526038610a2c5110157f01000000000000000000000000000000000000000000000000000000000000006001610a0c5101350402155857610a2c516109cc516104400152610a2c5160b66109ec5103610a0c51016109cc516104600137610a2c5160b66109ec5103610a0c510101610a0c526020610a2c51016109cc51016109cc5261027f565bfe5b5b5b81516001018083528114156100a4575b5050601f6109ac511115155857602060206109ac5102016109005260206109005103610a0c5261010060016064818352015b6000610a0c5112156102d45761030a565b61090051610a0c516040015101610a0c51610900516104400301526020610a0c5103610a0c5281516001018083528114156102c3575b50506109cc516109005101610420526109cc5161090051016109005161044003f35b61000461033003610004600039610004610330036000f31b2d4f" + address = inject_tx(tx) + assert viper_utils.bytes_to_int(address) == viper_utils.RLP_DECODER_ADDRESS + return address + return fake_tx diff --git a/tests/parser/functions/rlp/test_block_number.py b/tests/parser/functions/rlp/test_block_number.py new file mode 100644 index 0000000000..fb7a283f08 --- /dev/null +++ b/tests/parser/functions/rlp/test_block_number.py @@ -0,0 +1,10 @@ +def test_block_number(get_contract_with_gas_estimation, fake_tx): + fake_tx() + + block_number_code = """ +@public +def block_number() -> num: + return block.number +""" + c = get_contract_with_gas_estimation(block_number_code) + assert c.block_number() == 2 diff --git a/tests/parser/functions/test_rlp_list.py b/tests/parser/functions/rlp/test_rlp_list.py similarity index 93% rename from tests/parser/functions/test_rlp_list.py rename to tests/parser/functions/rlp/test_rlp_list.py index 8d2b283568..465ce57ce4 100644 --- a/tests/parser/functions/test_rlp_list.py +++ b/tests/parser/functions/rlp/test_rlp_list.py @@ -1,9 +1,8 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, assert_tx_failed, rlp +import rlp +def test_rlp_decoder_code(assert_tx_failed, get_contract_with_gas_estimation, fake_tx): + fake_tx() -def test_rlp_decoder_code(assert_tx_failed): rlp_decoder_code = """ u: bytes <= 100 diff --git a/tests/parser/functions/test_as_num256.py b/tests/parser/functions/test_as_num256.py index d784c5f33d..4c8c83b2f5 100644 --- a/tests/parser/functions/test_as_num256.py +++ b/tests/parser/functions/test_as_num256.py @@ -1,8 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - -def test_as_num256_with_negative_num(assert_compile_failed): +def test_as_num256_with_negative_num(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def foo() -> num256: @@ -11,7 +7,7 @@ def foo() -> num256: assert_compile_failed(lambda: get_contract_with_gas_estimation(code), Exception) -def test_as_num256_with_negative_input(assert_tx_failed): +def test_as_num256_with_negative_input(assert_tx_failed, get_contract_with_gas_estimation): code = """ @public def foo(x: num) -> num256: diff --git a/tests/parser/functions/test_bitwise.py b/tests/parser/functions/test_bitwise.py index 9ab62c0a04..105e3bc0ae 100644 --- a/tests/parser/functions/test_bitwise.py +++ b/tests/parser/functions/test_bitwise.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_test_bitwise(): +def test_test_bitwise(get_contract_with_gas_estimation): test_bitwise = """ @public def _bitwise_and(x: num256, y: num256) -> num256: diff --git a/tests/parser/functions/test_block_number.py b/tests/parser/functions/test_block_number.py deleted file mode 100644 index c5b65114c3..0000000000 --- a/tests/parser/functions/test_block_number.py +++ /dev/null @@ -1,13 +0,0 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_block_number(): - block_number_code = """ -@public -def block_number() -> num: - return block.number -""" - c = get_contract_with_gas_estimation(block_number_code) - c.block_number() == 2 diff --git a/tests/parser/functions/test_concat.py b/tests/parser/functions/test_concat.py index 5d760a72b8..6a5fd44c84 100644 --- a/tests/parser/functions/test_concat.py +++ b/tests/parser/functions/test_concat.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_concat(): +def test_concat(get_contract_with_gas_estimation): test_concat = """ @public def foo2(input1: bytes <= 50, input2: bytes <= 50) -> bytes <= 1000: @@ -26,7 +21,7 @@ def foo3(input1: bytes <= 50, input2: bytes <= 50, input3: bytes <= 50) -> bytes print('Passed simple concat test') -def test_concat2(): +def test_concat2(get_contract_with_gas_estimation): test_concat2 = """ @public def foo(inp: bytes <= 50) -> bytes <= 1000: @@ -39,7 +34,7 @@ def foo(inp: bytes <= 50) -> bytes <= 1000: print('Passed second concat test') -def test_crazy_concat_code(): +def test_crazy_concat_code(get_contract_with_gas_estimation): crazy_concat_code = """ y: bytes <= 10 @@ -57,7 +52,7 @@ def krazykonkat(z: bytes <= 10) -> bytes <= 25: print('Passed third concat test') -def test_concat_bytes32(): +def test_concat_bytes32(get_contract_with_gas_estimation): test_concat_bytes32 = """ @public def sandwich(inp: bytes <= 100, inp2: bytes32) -> bytes <= 164: @@ -79,7 +74,7 @@ def fivetimes(inp: bytes32) -> bytes <= 160: print("Passed concat bytes32 test") -def test_konkat_code(): +def test_konkat_code(get_contract_with_gas_estimation): konkat_code = """ ecks: bytes32 diff --git a/tests/parser/functions/test_ec.py b/tests/parser/functions/test_ec.py index d2b02b8fb9..8df3935fc6 100644 --- a/tests/parser/functions/test_ec.py +++ b/tests/parser/functions/test_ec.py @@ -1,10 +1,23 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, G1, G1_times_two, G1_times_three, \ - curve_order, negative_G1 +G1 = [1, 2] +G1_times_two = [ + 1368015179489954701390400359078579693043519447331113978918064868415326638035, + 9918110051302171585080402603319702774565515993150576347155970296011118125764 +] -def test_ecadd(): +G1_times_three = [ + 3353031288059533942658390886683067124040920775575537747144343083137631628272, + 19321533766552368860946552437480515441416830039777911637913418824951667761761 +] + +negative_G1 = [ + 1, + 21888242871839275222246405745257275088696311157297823662689037894645226208581 +] + +curve_order = 21888242871839275222246405745257275088548364400416034343698204186575808495617 + +def test_ecadd(get_contract_with_gas_estimation): ecadder = """ x3: num256[2] y3: num256[2] @@ -33,7 +46,7 @@ def _ecadd3(x: num256[2], y: num256[2]) -> num256[2]: assert c._ecadd3(G1, [0, 0]) == G1 assert c._ecadd3(G1, negative_G1) == [0, 0] -def test_ecmul(): +def test_ecmul(get_contract_with_gas_estimation): ecmuller = """ x3: num256[2] y3: num256 diff --git a/tests/parser/functions/test_ecrecover.py b/tests/parser/functions/test_ecrecover.py index 9aa177c944..d58e893990 100644 --- a/tests/parser/functions/test_ecrecover.py +++ b/tests/parser/functions/test_ecrecover.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_ecrecover_test(): +def test_ecrecover_test(get_contract_with_gas_estimation, utils): ecrecover_test = """ @public def test_ecrecover(h: bytes32, v:num256, r:num256, s:num256) -> address: @@ -20,8 +15,8 @@ def test_ecrecover2() -> address: c = get_contract_with_gas_estimation(ecrecover_test) h = b'\x35' * 32 k = b'\x46' * 32 - v, r, S = u.ecsign(h, k) - assert c.test_ecrecover(h, v, r, S) == '0x' + u.encode_hex(u.privtoaddr(k)) - assert c.test_ecrecover2() == '0x' + u.encode_hex(u.privtoaddr(k)) + v, r, S = utils.ecsign(h, k) + assert c.test_ecrecover(h, v, r, S) == '0x' + utils.encode_hex(utils.privtoaddr(k)) + assert c.test_ecrecover2() == '0x' + utils.encode_hex(utils.privtoaddr(k)) print("Passed ecrecover test") diff --git a/tests/parser/functions/test_extract32.py b/tests/parser/functions/test_extract32.py index f7cf60ad34..c73454a146 100644 --- a/tests/parser/functions/test_extract32.py +++ b/tests/parser/functions/test_extract32.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_extract32_code(): +def test_extract32_code(get_contract_with_gas_estimation): extract32_code = """ y: bytes <= 100 @public @@ -56,7 +51,7 @@ def extrakt32_storage(index: num, inp: bytes <= 100) -> bytes32: print("Passed bytes32 extraction test") -def test_extract32_code(): +def test_extract32_code(get_contract_with_gas_estimation): extract32_code = """ @public def foo(inp: bytes <= 32) -> num: diff --git a/tests/parser/functions/test_is_contract.py b/tests/parser/functions/test_is_contract.py index 961fec3fe1..d0ab03a830 100644 --- a/tests/parser/functions/test_is_contract.py +++ b/tests/parser/functions/test_is_contract.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_is_contract(): +def test_is_contract(t, get_contract_with_gas_estimation): contract_1 = """ @public def foo(arg1: address) -> bool: diff --git a/tests/parser/functions/test_length.py b/tests/parser/functions/test_length.py index be71c5f1fb..42efadaf4f 100644 --- a/tests/parser/functions/test_length.py +++ b/tests/parser/functions/test_length.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_test_length(): +def test_test_length(get_contract_with_gas_estimation): test_length = """ y: bytes <= 10 diff --git a/tests/parser/functions/test_method_id.py b/tests/parser/functions/test_method_id.py index fe0228a9e2..da4cfd69f6 100644 --- a/tests/parser/functions/test_method_id.py +++ b/tests/parser/functions/test_method_id.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_method_id_test(): +def test_method_id_test(get_contract_with_gas_estimation): method_id_test = """ @public def double(x: num) -> num: diff --git a/tests/parser/functions/test_minmax.py b/tests/parser/functions/test_minmax.py index cbe94e3ac4..f5f05df9bb 100644 --- a/tests/parser/functions/test_minmax.py +++ b/tests/parser/functions/test_minmax.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_minmax(): +def test_minmax(get_contract_with_gas_estimation): minmax_test = """ @public def foo() -> decimal: diff --git a/tests/parser/functions/test_raw_call.py b/tests/parser/functions/test_raw_call.py index f1a4eb29e9..9dcc87cd97 100644 --- a/tests/parser/functions/test_raw_call.py +++ b/tests/parser/functions/test_raw_call.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_caller_code(): +def test_caller_code(get_contract_with_gas_estimation): caller_code = """ @public def foo() -> bytes <= 7: @@ -27,7 +22,7 @@ def baz() -> bytes <= 7: -def test_multiple_levels(): +def test_multiple_levels(get_contract_with_gas_estimation, chain): inner_code = """ @public def returnten() -> num: @@ -53,15 +48,15 @@ def create_and_return_forwarder(inp: address) -> address: assert c2.create_and_call_returnten(c.address) == 10 expected_forwarder_code_mask = b'`.`\x0c`\x009`.`\x00\xf36`\x00`\x007a\x10\x00`\x006`\x00s\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Z\xf4\x15XWa\x10\x00`\x00\xf3'[12:] c3 = c2.create_and_return_forwarder(c.address) - assert s.head_state.get_code(c3)[:15] == expected_forwarder_code_mask[:15] - assert s.head_state.get_code(c3)[35:] == expected_forwarder_code_mask[35:] + assert chain.head_state.get_code(c3)[:15] == expected_forwarder_code_mask[:15] + assert chain.head_state.get_code(c3)[35:] == expected_forwarder_code_mask[35:] print('Passed forwarder test') # TODO: This one is special - print('Gas consumed: %d' % (s.head_state.receipts[-1].gas_used - s.head_state.receipts[-2].gas_used - s.last_tx.intrinsic_gas_used)) + print('Gas consumed: %d' % (chain.head_state.receipts[-1].gas_used - chain.head_state.receipts[-2].gas_used - chain.last_tx.intrinsic_gas_used)) -def test_multiple_levels2(): +def test_multiple_levels2(get_contract_with_gas_estimation): inner_code = """ @public def returnten() -> num: diff --git a/tests/parser/functions/test_return_tuple.py b/tests/parser/functions/test_return_tuple.py index 9b87c2061a..f8e54aea20 100644 --- a/tests/parser/functions/test_return_tuple.py +++ b/tests/parser/functions/test_return_tuple.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_return_type(): +def test_return_type(get_contract_with_gas_estimation): long_string = 35 * "test" code = """ @@ -64,7 +59,7 @@ def out_very_long_bytes() -> (num, bytes <= 1024, num, address): assert c.out_very_long_bytes() == [5555, long_string.encode(), 6666, "0x0000000000000000000000000000000000001234"] -def test_return_type_signatures(): +def test_return_type_signatures(get_contract_with_gas_estimation): code = """ @public def out_literals() -> (num, address, bytes <= 4): diff --git a/tests/parser/functions/test_send.py b/tests/parser/functions/test_send.py index e39dd5a700..1e5b75e9ff 100644 --- a/tests/parser/functions/test_send.py +++ b/tests/parser/functions/test_send.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_send(assert_tx_failed): +def test_send(assert_tx_failed, chain): send_test = """ @public def foo(): @@ -13,7 +8,7 @@ def foo(): def fop(): send(msg.sender, 10) """ - c = s.contract(send_test, language='viper', value=10) + c = chain.contract(send_test, language='viper', value=10) assert_tx_failed(lambda: c.foo()) c.fop() assert_tx_failed(lambda: c.fop()) diff --git a/tests/parser/functions/test_sha3.py b/tests/parser/functions/test_sha3.py index 249e83b371..65cb7cdecb 100644 --- a/tests/parser/functions/test_sha3.py +++ b/tests/parser/functions/test_sha3.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_hash_code(): +def test_hash_code(get_contract_with_gas_estimation, utils): hash_code = """ @public def foo(inp: bytes <= 100) -> bytes32: @@ -16,12 +11,12 @@ def bar() -> bytes32: c = get_contract_with_gas_estimation(hash_code) for inp in (b"", b"cow", b"s" * 31, b"\xff" * 32, b"\n" * 33, b"g" * 64, b"h" * 65): - assert c.foo(inp) == u.sha3(inp) + assert c.foo(inp) == utils.sha3(inp) - assert c.bar() == u.sha3("inp") + assert c.bar() == utils.sha3("inp") -def test_hash_code2(): +def test_hash_code2(get_contract_with_gas_estimation): hash_code2 = """ @public def foo(inp: bytes <= 100) -> bool: @@ -32,7 +27,7 @@ def foo(inp: bytes <= 100) -> bool: assert c.foo(b"badminton") is True -def test_hash_code3(): +def test_hash_code3(get_contract_with_gas_estimation): hash_code3 = """ test: bytes <= 100 diff --git a/tests/parser/functions/test_slice.py b/tests/parser/functions/test_slice.py index 29324c632f..6e8dd50a47 100644 --- a/tests/parser/functions/test_slice.py +++ b/tests/parser/functions/test_slice.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - - -def test_test_slice(): +def test_test_slice(get_contract_with_gas_estimation): test_slice = """ @public @@ -30,7 +25,7 @@ def bar(inp1: bytes <= 10) -> num: print('Passed slice test') -def test_test_slice2(): +def test_test_slice2(get_contract_with_gas_estimation): test_slice2 = """ @public def slice_tower_test(inp1: bytes <= 50) -> bytes <= 50: @@ -47,7 +42,7 @@ def slice_tower_test(inp1: bytes <= 50) -> bytes <= 50: print('Passed advanced slice test') -def test_test_slice3(): +def test_test_slice3(get_contract_with_gas_estimation): test_slice3 = """ x: num s: bytes <= 50 @@ -76,7 +71,7 @@ def bar(inp1: bytes <= 50) -> num: print('Passed storage slice test') -def test_test_slice4(): +def test_test_slice4(get_contract_with_gas_estimation): test_slice4 = """ @public def foo(inp: bytes <= 10, start: num, len: num) -> bytes <= 10: diff --git a/tests/parser/globals/test_getters.py b/tests/parser/globals/test_getters.py index ba5fe91a6e..07235f864d 100644 --- a/tests/parser/globals/test_getters.py +++ b/tests/parser/globals/test_getters.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation_for_constants - - -def test_state_accessor(): +def test_state_accessor(get_contract_with_gas_estimation_for_constants): state_accessor = """ y: num[num] @@ -23,7 +18,7 @@ def foo() -> num: print('Passed basic state accessor test') -def test_getter_code(): +def test_getter_code(get_contract_with_gas_estimation_for_constants): getter_code = """ x: public(wei_value) y: public(num[5]) diff --git a/tests/parser/globals/test_globals.py b/tests/parser/globals/test_globals.py index 7608990b12..36e48e47e8 100644 --- a/tests/parser/globals/test_globals.py +++ b/tests/parser/globals/test_globals.py @@ -1,8 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation - -def test_permanent_variables_test(): +def test_permanent_variables_test(get_contract_with_gas_estimation): permanent_variables_test = """ var: {a: num, b: num} diff --git a/tests/parser/globals/test_setters.py b/tests/parser/globals/test_setters.py index 0e49e0acc3..ec389b35cb 100644 --- a/tests/parser/globals/test_setters.py +++ b/tests/parser/globals/test_setters.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_multi_setter_test(): +def test_multi_setter_test(get_contract_with_gas_estimation): multi_setter_test = """ foo: num[3] bar: num[3][3] @@ -74,7 +69,7 @@ def jop() -> num: print('Passed multi-setter literal test') -def test_multi_setter_struct_test(): +def test_multi_setter_struct_test(get_contract_with_gas_estimation): multi_setter_struct_test = """ foo: {foo: num, bar: num}[3] z: {foo: num[3], bar: {a: num, b: num}[2]}[2] @@ -126,7 +121,7 @@ def gop() -> num: print('Passed multi-setter struct test') -def test_type_converter_setter_test(): +def test_type_converter_setter_test(get_contract_with_gas_estimation): type_converter_setter_test = """ mom: {a: {c: num}[3], b: num} non: {a: {c: decimal}[3], b:num} @@ -150,7 +145,7 @@ def goo() -> num: print('Passed type-conversion struct test') -def test_composite_setter_test(): +def test_composite_setter_test(get_contract_with_gas_estimation): composite_setter_test = """ mom: {a: {c: num}[3], b:num} qoq: {c: num} diff --git a/tests/parser/integration/test_basics.py b/tests/parser/integration/test_basics.py index 47b8c70e07..f96c899980 100644 --- a/tests/parser/integration/test_basics.py +++ b/tests/parser/integration/test_basics.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_null_code(): +def test_null_code(get_contract_with_gas_estimation): null_code = """ @public def foo(): @@ -14,7 +9,7 @@ def foo(): print('Successfully executed a null function') -def test_basic_code(): +def test_basic_code(get_contract_with_gas_estimation): basic_code = """ @public def foo(x: num) -> num: @@ -26,7 +21,7 @@ def foo(x: num) -> num: print('Passed basic code test') -def test_selfcall_code_3(): +def test_selfcall_code_3(get_contract_with_gas_estimation, utils): selfcall_code_3 = """ @public def _hashy2(x: bytes <= 100) -> bytes32: @@ -46,7 +41,7 @@ def returnten() -> num: """ c = get_contract_with_gas_estimation(selfcall_code_3) - assert c.return_hash_of_cow_x_30() == u.sha3(b'cow' * 30) + assert c.return_hash_of_cow_x_30() == utils.sha3(b'cow' * 30) assert c.returnten() == 10 print("Passed single variable-size argument self-call test") diff --git a/tests/parser/integration/test_crowdfund.py b/tests/parser/integration/test_crowdfund.py index 042246f589..93acb8e4b1 100644 --- a/tests/parser/integration/test_crowdfund.py +++ b/tests/parser/integration/test_crowdfund.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation_for_constants - - -def test_crowdfund(): +def test_crowdfund(t, chain, get_contract_with_gas_estimation_for_constants): crowdfund = """ funders: {sender: address, value: wei_value}[num] @@ -82,11 +77,11 @@ def refund(): assert not c.reached() c.participate(value=49) assert c.reached() - pre_bal = s.head_state.get_balance(t.a1) - s.head_state.timestamp += 1000 + pre_bal = chain.head_state.get_balance(t.a1) + chain.head_state.timestamp += 1000 assert c.expired() c.finalize() - post_bal = s.head_state.get_balance(t.a1) + post_bal = chain.head_state.get_balance(t.a1) assert post_bal - pre_bal == 54 c = get_contract_with_gas_estimation_for_constants(crowdfund, args=[t.a1, 50, 600]) @@ -94,18 +89,18 @@ def refund(): c.participate(value=2, sender=t.k4) c.participate(value=3, sender=t.k5) c.participate(value=4, sender=t.k6) - s.head_state.timestamp += 1000 + chain.head_state.timestamp += 1000 assert c.expired() assert not c.reached() - pre_bals = [s.head_state.get_balance(x) for x in [t.a3, t.a4, t.a5, t.a6]] + pre_bals = [chain.head_state.get_balance(x) for x in [t.a3, t.a4, t.a5, t.a6]] c.refund() - post_bals = [s.head_state.get_balance(x) for x in [t.a3, t.a4, t.a5, t.a6]] + post_bals = [chain.head_state.get_balance(x) for x in [t.a3, t.a4, t.a5, t.a6]] assert [y - x for x, y in zip(pre_bals, post_bals)] == [1, 2, 3, 4] print('Passed composite crowdfund test') -def test_crowdfund2(): +def test_crowdfund2(t, chain, get_contract_with_gas_estimation_for_constants): crowdfund2 = """ funders: {sender: address, value: wei_value}[num] @@ -183,11 +178,11 @@ def refund(): assert not c.reached() c.participate(value=49) assert c.reached() - pre_bal = s.head_state.get_balance(t.a1) - s.head_state.timestamp += 1000 + pre_bal = chain.head_state.get_balance(t.a1) + chain.head_state.timestamp += 1000 assert c.expired() c.finalize() - post_bal = s.head_state.get_balance(t.a1) + post_bal = chain.head_state.get_balance(t.a1) assert post_bal - pre_bal == 54 c = get_contract_with_gas_estimation_for_constants(crowdfund2, args=[t.a1, 50, 600]) @@ -195,12 +190,12 @@ def refund(): c.participate(value=2, sender=t.k4) c.participate(value=3, sender=t.k5) c.participate(value=4, sender=t.k6) - s.head_state.timestamp += 1000 + chain.head_state.timestamp += 1000 assert c.expired() assert not c.reached() - pre_bals = [s.head_state.get_balance(x) for x in [t.a3, t.a4, t.a5, t.a6]] + pre_bals = [chain.head_state.get_balance(x) for x in [t.a3, t.a4, t.a5, t.a6]] c.refund() - post_bals = [s.head_state.get_balance(x) for x in [t.a3, t.a4, t.a5, t.a6]] + post_bals = [chain.head_state.get_balance(x) for x in [t.a3, t.a4, t.a5, t.a6]] assert [y - x for x, y in zip(pre_bals, post_bals)] == [1, 2, 3, 4] print('Passed second composite crowdfund test') diff --git a/tests/parser/integration/test_escrow.py b/tests/parser/integration/test_escrow.py index 77a5d0b4c0..95fd5fdc77 100644 --- a/tests/parser/integration/test_escrow.py +++ b/tests/parser/integration/test_escrow.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_arbitration_code(): +def test_arbitration_code(t, get_contract_with_gas_estimation): arbitration_code = """ buyer: address seller: address @@ -41,7 +36,7 @@ def refund(): print('Passed escrow test') -def test_arbitration_code_with_init(): +def test_arbitration_code_with_init(t, get_contract_with_gas_estimation): arbitration_code_with_init = """ buyer: address seller: address diff --git a/tests/parser/types/numbers/test_decimals.py b/tests/parser/types/numbers/test_decimals.py index 7c86410508..5f55d35ebf 100644 --- a/tests/parser/types/numbers/test_decimals.py +++ b/tests/parser/types/numbers/test_decimals.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_decimal_test(): +def test_decimal_test(chain, check_gas, get_contract_with_gas_estimation): decimal_test = """ @public def foo() -> num: @@ -64,7 +59,7 @@ def foop() -> num: """ c = get_contract_with_gas_estimation(decimal_test) - pre_txs = len(s.head_state.receipts) + pre_txs = len(chain.head_state.receipts) assert c.foo() == 999 assert c.fop() == 999 assert c.foq() == 999 @@ -79,13 +74,13 @@ def foop() -> num: assert c.foom() == 999 assert c.foon() == 999 assert c.foop() == 999 - post_txs = len(s.head_state.receipts) + post_txs = len(chain.head_state.receipts) print('Passed basic addition, subtraction and multiplication tests') check_gas(decimal_test, num_txs=(post_txs - pre_txs)) -def test_harder_decimal_test(): +def test_harder_decimal_test(get_contract_with_gas_estimation): harder_decimal_test = """ @public def phooey(inp: decimal) -> decimal: diff --git a/tests/parser/types/numbers/test_num.py b/tests/parser/types/numbers/test_num.py index 21d57e0677..b67d50631f 100644 --- a/tests/parser/types/numbers/test_num.py +++ b/tests/parser/types/numbers/test_num.py @@ -1,10 +1,7 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract, assert_tx_failed from viper.exceptions import TypeMismatchException -def test_exponents_with_nums(): +def test_exponents_with_nums(get_contract_with_gas_estimation): exp_code = """ @public def _num_exp(x: num, y: num) -> num: @@ -20,7 +17,7 @@ def _num_exp(x: num, y: num) -> num: assert c._num_exp(72,19) == 72**19 -def test_negative_nums(assert_tx_failed): +def test_negative_nums(t, get_contract_with_gas_estimation, chain): negative_nums_code = """ @public def _negative_num() -> num: @@ -32,12 +29,12 @@ def _negative_exp() -> num: """ c = get_contract_with_gas_estimation(negative_nums_code) - t.s = s + t.s = chain assert c._negative_num() == -1 assert c._negative_exp() == -3 -def test_exponents_with_units(assert_compile_failed): +def test_exponents_with_units(get_contract_with_gas_estimation): code = """ @public def foo() -> num(wei): @@ -53,7 +50,7 @@ def foo() -> num(wei): assert c.foo() == 4 -def test_num_bound(assert_tx_failed): +def test_num_bound(t, assert_tx_failed, get_contract_with_gas_estimation, chain): num_bound_code = """ @public def _num(x: num) -> num: @@ -82,7 +79,7 @@ def _num_min() -> num: c = get_contract_with_gas_estimation(num_bound_code) - t.s = s + t.s = chain NUM_MAX = 2**127 - 1 NUM_MIN = -2**127 assert c._num_add(NUM_MAX, 0) == NUM_MAX @@ -100,7 +97,7 @@ def _num_min() -> num: assert c._num_add3(NUM_MAX, -1, 1) == NUM_MAX -def test_invalid_unit_exponent(assert_compile_failed): +def test_invalid_unit_exponent(assert_compile_failed, get_contract_with_gas_estimation): code = """ @public def foo(): diff --git a/tests/parser/types/numbers/test_num256.py b/tests/parser/types/numbers/test_num256.py index c576930ec0..eea78ecdbb 100644 --- a/tests/parser/types/numbers/test_num256.py +++ b/tests/parser/types/numbers/test_num256.py @@ -1,10 +1,7 @@ -import pytest from ethereum.abi import ValueOutOfBounds -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract, assert_tx_failed -def test_num256_code(assert_tx_failed): +def test_num256_code(t, chain, assert_tx_failed, get_contract_with_gas_estimation): num256_code = """ @public def _num256_add(x: num256, y: num256) -> num256: @@ -43,7 +40,7 @@ def _num256_le(x: num256, y: num256) -> bool: x = 126416208461208640982146408124 y = 7128468721412412459 - t.s = s + t.s = chain NUM256_MAX = 2**256 -1 # Max possible num256 value assert c._num256_add(x, y) == x + y assert c._num256_add(0,y) == y @@ -75,7 +72,7 @@ def _num256_le(x: num256, y: num256) -> bool: print("Passed num256 operation tests") -def test_num256_mod(assert_tx_failed): +def test_num256_mod(t, chain, assert_tx_failed, get_contract_with_gas_estimation): num256_code = """ @public def _num256_mod(x: num256, y: num256) -> num256: @@ -91,7 +88,7 @@ def _num256_mulmod(x: num256, y: num256, z: num256) -> num256: """ c = get_contract_with_gas_estimation(num256_code) - t.s = s + t.s = chain assert c._num256_mod(3, 2) == 1 assert c._num256_mod(34, 32) == 2 @@ -105,7 +102,7 @@ def _num256_mulmod(x: num256, y: num256, z: num256) -> num256: assert_tx_failed(lambda: c._num256_mulmod(2**255, 2, 1)) -def test_num256_with_exponents(assert_tx_failed): +def test_num256_with_exponents(t, chain, assert_tx_failed, get_contract_with_gas_estimation): exp_code = """ @public def _num256_exp(x: num256, y: num256) -> num256: @@ -113,7 +110,7 @@ def _num256_exp(x: num256, y: num256) -> num256: """ c = get_contract_with_gas_estimation(exp_code) - t.s = s + t.s = chain assert c._num256_exp(2, 0) == 1 assert c._num256_exp(2, 1) == 2 @@ -123,7 +120,7 @@ def _num256_exp(x: num256, y: num256) -> num256: assert c._num256_exp(7**23, 3) == 7**69 -def test_num256_to_num_casting(assert_tx_failed): +def test_num256_to_num_casting(t, chain, assert_tx_failed, get_contract_with_gas_estimation): code = """ @public def _num256_to_num(x: num(num256)) -> num: @@ -145,7 +142,7 @@ def built_in_conversion(x: num256) -> num: assert c._num256_to_num(1) == 1 assert c._num256_to_num((2**127) - 1) == 2**127 - 1 - t.s = s + t.s = chain assert_tx_failed(lambda: c._num256_to_num((2**128)) == 0) assert c._num256_to_num_call(1) == 1 @@ -158,7 +155,7 @@ def built_in_conversion(x: num256) -> num: assert_tx_failed(lambda: c._num256_to_num_call(2**127)) -def test_modmul(): +def test_modmul(get_contract_with_gas_estimation): modexper = """ @public def exp(base: num256, exponent: num256, modulus: num256) -> num256: diff --git a/tests/parser/types/test_bytes.py b/tests/parser/types/test_bytes.py index 55fff53254..6d63198531 100644 --- a/tests/parser/types/test_bytes.py +++ b/tests/parser/types/test_bytes.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_test_bytes(): +def test_test_bytes(get_contract_with_gas_estimation): test_bytes = """ @public def foo(x: bytes <= 100) -> bytes <= 100: @@ -29,7 +24,7 @@ def foo(x: bytes <= 100) -> bytes <= 100: print('Passed input-too-long test') -def test_test_bytes2(): +def test_test_bytes2(get_contract_with_gas_estimation): test_bytes2 = """ @public def foo(x: bytes <= 100) -> bytes <= 100: @@ -47,7 +42,7 @@ def foo(x: bytes <= 100) -> bytes <= 100: print('Passed string copying test') -def test_test_bytes3(): +def test_test_bytes3(get_contract_with_gas_estimation): test_bytes3 = """ x: num maa: bytes <= 60 @@ -98,7 +93,7 @@ def get_xy() -> num: print('Passed advanced string copying test') -def test_test_bytes4(): +def test_test_bytes4(get_contract_with_gas_estimation): test_bytes4 = """ a: bytes <= 60 @public @@ -121,7 +116,7 @@ def bar(inp: bytes <= 60) -> bytes <= 60: print('Passed string deleting test') -def test_test_bytes5(): +def test_test_bytes5(get_contract_with_gas_estimation): test_bytes5 = """ g: {a: bytes <= 50, b: bytes <= 50} @@ -166,7 +161,7 @@ def quz(inp1: bytes <= 40, inp2: bytes <= 45): print('Passed string struct test') -def test_bytes_to_num_code(): +def test_bytes_to_num_code(get_contract_with_gas_estimation): bytes_to_num_code = """ @public def foo(x: bytes <= 32) -> num: @@ -196,9 +191,11 @@ def foo(x: bytes <= 32) -> num: success = True except: success = False + assert not success try: c.foo(b"\x01" * 33) success = True except: success = False + assert not success print('Passed bytes_to_num tests') diff --git a/tests/parser/types/test_lists.py b/tests/parser/types/test_lists.py index 516774a7e2..3abfee9472 100644 --- a/tests/parser/types/test_lists.py +++ b/tests/parser/types/test_lists.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_list_tester_code(): +def test_list_tester_code(get_contract_with_gas_estimation): list_tester_code = """ z: num[3] z2: num[2][2] @@ -50,7 +45,7 @@ def loo(x: num[2][2]) -> num: print("Passed list tests") -def test_list_output_tester_code(): +def test_list_output_tester_code(get_contract_with_gas_estimation): list_output_tester_code = """ z: num[2] @@ -121,7 +116,7 @@ def roo(inp: num[2]) -> decimal[2][2]: print("Passed list output tests") -def test_array_accessor(): +def test_array_accessor(get_contract_with_gas_estimation): array_accessor = """ @public def test_array(x: num, y: num, z: num, w: num) -> num: @@ -138,7 +133,7 @@ def test_array(x: num, y: num, z: num, w: num) -> num: print('Passed basic array accessor test') -def test_two_d_array_accessor(): +def test_two_d_array_accessor(get_contract_with_gas_estimation): two_d_array_accessor = """ @public def test_array(x: num, y: num, z: num, w: num) -> num: diff --git a/tests/parser/types/test_string_literal.py b/tests/parser/types/test_string_literal.py index 63791929d0..bd3b2773da 100644 --- a/tests/parser/types/test_string_literal.py +++ b/tests/parser/types/test_string_literal.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_string_literal_code(): +def test_string_literal_code(get_contract_with_gas_estimation): string_literal_code = """ @public def foo() -> bytes <= 5: @@ -42,7 +37,7 @@ def baz4() -> bytes <= 100: print("Passed string literal test") -def test_string_literal_splicing_fuzz(): +def test_string_literal_splicing_fuzz(get_contract_with_gas_estimation): for i in range(95, 96, 97): kode = """ moo: bytes <= 100 diff --git a/tests/parser/types/value/test_wei.py b/tests/parser/types/value/test_wei.py index 9bbfb061bf..f2a41d1b68 100644 --- a/tests/parser/types/value/test_wei.py +++ b/tests/parser/types/value/test_wei.py @@ -1,9 +1,4 @@ -import pytest -from tests.setup_transaction_tests import chain as s, tester as t, ethereum_utils as u, check_gas, \ - get_contract_with_gas_estimation, get_contract - - -def test_test_wei(): +def test_test_wei(get_contract_with_gas_estimation): test_wei = """ @public def return_2_finney() -> wei_value: diff --git a/tests/setup_transaction_tests.py b/tests/setup_transaction_tests.py deleted file mode 100644 index c889356e20..0000000000 --- a/tests/setup_transaction_tests.py +++ /dev/null @@ -1,148 +0,0 @@ -import pytest -from functools import wraps - -from viper import parser, compile_lll, utils -from viper import compiler -from ethereum.tools import tester -from ethereum import transactions, messages -from ethereum import utils as ethereum_utils -import rlp -from ethereum.slogging import LogRecorder, configure_logging, set_level -config_string = ':info,eth.vm.log:trace,eth.vm.op:trace,eth.vm.stack:trace,eth.vm.exit:trace,eth.pb.msg:trace,eth.pb.tx:debug' -#configure_logging(config_string=config_string) - -chain = tester.Chain() -tester.languages['viper'] = compiler.Compiler() - -def inject_tx(txhex): - tx = rlp.decode(ethereum_utils.decode_hex(txhex[2:]), transactions.Transaction) - chain.head_state.set_balance(tx.sender, tx.startgas * tx.gasprice) - chain.chain.state.set_balance(tx.sender, tx.startgas * tx.gasprice) - messages.apply_transaction(chain.head_state, tx) - chain.block.transactions.append(tx) - contract_address = ethereum_utils.sha3(rlp.encode([tx.sender, 0]))[12:] - assert chain.head_state.get_code(contract_address) - chain.mine(1) - return contract_address - -_rlp_decoder_address = inject_tx("0xf9035b808506fc23ac0083045ef88080b903486103305660006109ac5260006109cc527f0100000000000000000000000000000000000000000000000000000000000000600035046109ec526000610a0c5260006109005260c06109ec51101515585760f86109ec51101561006e5760bf6109ec510336141558576001610a0c52610098565b60013560f76109ec51036020035260005160f66109ec510301361415585760f66109ec5103610a0c525b61010060016064818352015b36610a0c511015156100b557610291565b7f0100000000000000000000000000000000000000000000000000000000000000610a0c5135046109ec526109cc5160206109ac51026040015260016109ac51016109ac5260806109ec51101561013b5760016109cc5161044001526001610a0c516109cc5161046001376001610a0c5101610a0c5260216109cc51016109cc52610281565b60b86109ec5110156101d15760806109ec51036109cc51610440015260806109ec51036001610a0c51016109cc51610460013760816109ec5114156101ac5760807f01000000000000000000000000000000000000000000000000000000000000006001610a0c5101350410151558575b607f6109ec5103610a0c5101610a0c5260606109ec51036109cc51016109cc52610280565b60c06109ec51101561027d576001610a0c51013560b76109ec510360200352600051610a2c526038610a2c5110157f01000000000000000000000000000000000000000000000000000000000000006001610a0c5101350402155857610a2c516109cc516104400152610a2c5160b66109ec5103610a0c51016109cc516104600137610a2c5160b66109ec5103610a0c510101610a0c526020610a2c51016109cc51016109cc5261027f565bfe5b5b5b81516001018083528114156100a4575b5050601f6109ac511115155857602060206109ac5102016109005260206109005103610a0c5261010060016064818352015b6000610a0c5112156102d45761030a565b61090051610a0c516040015101610a0c51610900516104400301526020610a0c5103610a0c5281516001018083528114156102c3575b50506109cc516109005101610420526109cc5161090051016109005161044003f35b61000461033003610004600039610004610330036000f31b2d4f") -assert utils.bytes_to_int(_rlp_decoder_address) == utils.RLP_DECODER_ADDRESS - -chain.head_state.gas_limit = 10**9 - -def check_gas(code, func=None, num_txs=1): - if func: - gas_estimate = tester.languages['viper'].gas_estimate(code)[func] - else: - gas_estimate = sum(tester.languages['viper'].gas_estimate(code).values()) - gas_actual = chain.head_state.receipts[-1].gas_used \ - - chain.head_state.receipts[-1-num_txs].gas_used \ - - chain.last_tx.intrinsic_gas_used*num_txs - - # Computed upper bound on the gas consumption should - # be greater than or equal to the amount of gas used - if gas_estimate < gas_actual: - raise Exception("Gas upper bound fail: bound %d actual %d" % (gas_estimate, gas_actual)) - - print('Function name: {} - Gas estimate {}, Actual: {}'.format( - func, gas_estimate, gas_actual) - ) - - -def gas_estimation_decorator(fn, source_code, func): - def decorator(*args, **kwargs): - @wraps(fn) - def decorated_function(*args, **kwargs): - result = fn(*args, **kwargs) - check_gas(source_code, func) - return result - return decorated_function(*args, **kwargs) - return decorator - - -def set_decorator_to_contract_function(contract, source_code, func): - func_definition = getattr(contract, func) - func_with_decorator = gas_estimation_decorator( - func_definition, source_code, func - ) - setattr(contract, func, func_with_decorator) - - -def get_contract_with_gas_estimation( - source_code, - *args, **kwargs): - contract = chain.contract(source_code, language="viper", *args, **kwargs) - for func_name in contract.translator.function_data: - set_decorator_to_contract_function( - contract, source_code, func_name - ) - return contract - -def get_contract_with_gas_estimation_for_constants( - source_code, - *args, **kwargs): - abi = tester.languages['viper'].mk_full_signature(source_code) - # Take out constants from the abi for the purpose of gas estimation - for func in abi: - func['constant'] = False - ct = tester.ContractTranslator(abi) - byte_code = tester.languages['viper'].compile(source_code) + (ct.encode_constructor_arguments(kwargs['args']) if kwargs else b'') - address = chain.tx(to=b'', data=byte_code) - contract = tester.ABIContract(chain, abi, address) - for func_name in contract.translator.function_data: - set_decorator_to_contract_function( - contract, source_code, func_name - ) - return contract - -def get_contract(source_code, *args, **kwargs): - return chain.contract(source_code, language="viper", *args, **kwargs) - -G1 = [1, 2] - -G1_times_two = [ - 1368015179489954701390400359078579693043519447331113978918064868415326638035, - 9918110051302171585080402603319702774565515993150576347155970296011118125764 -] - -G1_times_three = [ - 3353031288059533942658390886683067124040920775575537747144343083137631628272, - 19321533766552368860946552437480515441416830039777911637913418824951667761761 -] - -negative_G1 = [ - 1, - 21888242871839275222246405745257275088696311157297823662689037894645226208581 -] - -curve_order = 21888242871839275222246405745257275088548364400416034343698204186575808495617 - -def get_logs(receipt, contract, event_name=None): - contract_log_ids = contract.translator.event_data.keys() # All the log ids contract has - # All logs originating from contract, and matching event_name (if specified) - logs = [log for log in receipt.logs \ - if log.topics[0] in contract_log_ids and \ - log.address == contract.address and \ - (not event_name or \ - contract.translator.event_data[log.topics[0]]['name'] == event_name)] - assert len(logs) > 0, "No logs in last receipt" - - # Return all events decoded in the receipt - return [contract.translator.decode_event(log.topics, log.data) for log in logs] - -@pytest.fixture -def get_last_log(): - def get_last_log(tester, contract, event_name=None): - receipt = tester.s.head_state.receipts[-1] # Only the receipts for the last block - # Get last log event with correct name and return the decoded event - return get_logs(receipt, contract, event_name=event_name)[-1] - return get_last_log - -@pytest.fixture -def assert_tx_failed(): - def assert_tx_failed(function_to_test, exception = tester.TransactionFailed): - initial_state = tester.s.snapshot() - with pytest.raises(exception): - function_to_test() - tester.s.revert(initial_state) - return assert_tx_failed From 17609e3b002106601ff967885bc2c34d3878ef4e Mon Sep 17 00:00:00 2001 From: James Ray Date: Sun, 10 Dec 2017 16:34:22 +1100 Subject: [PATCH 133/162] Update Safe Remote Purchases: line numbers, minor edits --- docs/viper-by-example.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/viper-by-example.rst b/docs/viper-by-example.rst index a0ad6bf7be..c8c16a758c 100644 --- a/docs/viper-by-example.rst +++ b/docs/viper-by-example.rst @@ -164,7 +164,7 @@ logic. Let's break down this contract bit by bit. .. literalinclude:: ../examples/safe_remote_purchase/safe_remote_purchase.v.py :language: python - :lines: 10-13 + :lines: 16-19 Like the other contracts, we begin by declaring our global variables public with their respective datatypes. Remember that the ``public`` function allows the @@ -172,7 +172,7 @@ variables to be *readable* by an external caller, but not *writeable*. .. literalinclude:: ../examples/safe_remote_purchase/safe_remote_purchase.v.py :language: python - :lines: 18-23 + :lines: 24-31 With a ``@payable`` decorator on the constructor, the contract creator will be required to make an initial deposit equal to twice the item's ``value`` to @@ -186,10 +186,10 @@ in the contract variable ``self.value`` and saves the contract creator into .. literalinclude:: ../examples/safe_remote_purchase/safe_remote_purchase.v.py :language: python - :lines: 25-28 + :lines: 33-38 The ``abort()`` method is a method only callable by the seller and while the -contract is still ``unlocked`` - meaning it is callable only prior to any buyer +contract is still ``unlocked``—meaning it is callable only prior to any buyer making a purchase. As we will see in the ``purchase()`` method that when a buyer calls the ``purchase()`` method and sends a valid amount to the contract, the contract will be locked and the seller will no longer be able to call @@ -201,23 +201,23 @@ subsequently destroys the contract. .. literalinclude:: ../examples/safe_remote_purchase/safe_remote_purchase.v.py :language: python - :lines: 30-35 + :lines: 40-46 Like the constructor, the ``purchase()`` method has a ``@payable`` decorator, meaning it can be called with a payment. For the buyer to make a valid -purchase, we must first ``assert`` that the contract's unlocked property is -``False`` and that the amount sent is equal to twice the item's value. We then +purchase, we must first ``assert`` that the contract's ``unlocked`` property is +``True`` and that the amount sent is equal to twice the item's value. We then set the buyer to the ``msg.sender`` and lock the contract. At this point, the contract has a balance equal to 4 times the item value and the seller must send the item to the buyer. .. literalinclude:: ../examples/safe_remote_purchase/safe_remote_purchase.v.py :language: python - :lines: 37-41 + :lines: 48-55 Finally, upon the buyer's receipt of the item, the buyer can confirm their receipt by calling the ``received()`` method to distribute the funds as -intended - the seller receives 3/4 of the contract balance and the buyer +intended—where the seller receives 3/4 of the contract balance and the buyer receives 1/4. By calling ``received()``, we begin by checking that the contract is indeed From abecf1e2da9efb99f84ba9cb63d89cb6d7b16f74 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Sun, 10 Dec 2017 11:40:16 -0800 Subject: [PATCH 134/162] Updates from code review --- .gitignore | 3 +- .../functions/{rlp => }/test_block_number.py | 4 +-- tests/parser/types/test_bytes.py | 30 ++++--------------- 3 files changed, 8 insertions(+), 29 deletions(-) rename tests/parser/functions/{rlp => }/test_block_number.py (69%) diff --git a/.gitignore b/.gitignore index 81eb1cbeeb..517ae4db53 100644 --- a/.gitignore +++ b/.gitignore @@ -30,5 +30,4 @@ docs/_build docs/modules.rst # IDEs -.idea/ -*.iml \ No newline at end of file +.idea/ \ No newline at end of file diff --git a/tests/parser/functions/rlp/test_block_number.py b/tests/parser/functions/test_block_number.py similarity index 69% rename from tests/parser/functions/rlp/test_block_number.py rename to tests/parser/functions/test_block_number.py index fb7a283f08..944a4a718b 100644 --- a/tests/parser/functions/rlp/test_block_number.py +++ b/tests/parser/functions/test_block_number.py @@ -1,5 +1,5 @@ -def test_block_number(get_contract_with_gas_estimation, fake_tx): - fake_tx() +def test_block_number(get_contract_with_gas_estimation, chain): + chain.mine(1) block_number_code = """ @public diff --git a/tests/parser/types/test_bytes.py b/tests/parser/types/test_bytes.py index 6d63198531..b099d4f382 100644 --- a/tests/parser/types/test_bytes.py +++ b/tests/parser/types/test_bytes.py @@ -161,7 +161,7 @@ def quz(inp1: bytes <= 40, inp2: bytes <= 45): print('Passed string struct test') -def test_bytes_to_num_code(get_contract_with_gas_estimation): +def test_bytes_to_num_code(get_contract_with_gas_estimation, assert_tx_failed): bytes_to_num_code = """ @public def foo(x: bytes <= 32) -> num: @@ -170,32 +170,12 @@ def foo(x: bytes <= 32) -> num: c = get_contract_with_gas_estimation(bytes_to_num_code) assert c.foo(b"") == 0 - try: - c.foo(b"\x00") - success = True - except: - success = False - assert not success + assert_tx_failed(lambda: c.foo(b"\x00")) assert c.foo(b"\x01") == 1 - try: - c.foo(b"\x00\x01") - success = True - except: - success = False - assert not success + assert_tx_failed(lambda: c.foo(b"\x00\x01")) assert c.foo(b"\x01\x00") == 256 assert c.foo(b"\x01\x00\x00\x00\x01") == 4294967297 assert c.foo(b"\xff" * 32) == -1 - try: - c.foo(b"\x80" + b"\xff" * 31) - success = True - except: - success = False - assert not success - try: - c.foo(b"\x01" * 33) - success = True - except: - success = False - assert not success + assert_tx_failed(lambda: c.foo(b"\x80" + b"\xff" * 31)) + assert_tx_failed(lambda: c.foo(b"\x01" * 33)) print('Passed bytes_to_num tests') From 441e41b5670dae5b59d3c68abcca8758c8c5a20f Mon Sep 17 00:00:00 2001 From: David Knott Date: Mon, 11 Dec 2017 03:43:38 +0100 Subject: [PATCH 135/162] Add modulo math checks --- tests/parser/types/numbers/test_num256.py | 4 ++++ viper/functions.py | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/parser/types/numbers/test_num256.py b/tests/parser/types/numbers/test_num256.py index eea78ecdbb..1c9ee69402 100644 --- a/tests/parser/types/numbers/test_num256.py +++ b/tests/parser/types/numbers/test_num256.py @@ -59,6 +59,7 @@ def _num256_le(x: num256, y: num256) -> bool: assert c._num256_div(x, y) == x // y assert_tx_failed(lambda: c._num256_div(NUM256_MAX, 0)) assert c._num256_div(y, x) == 0 + assert_tx_failed(lambda: c._num256_div(x, 0)) assert c._num256_gt(x, y) is True assert c._num256_ge(x, y) is True assert c._num256_le(x, y) is False @@ -92,14 +93,17 @@ def _num256_mulmod(x: num256, y: num256, z: num256) -> num256: assert c._num256_mod(3, 2) == 1 assert c._num256_mod(34, 32) == 2 + assert_tx_failed(lambda: c._num256_mod(3, 0)) assert c._num256_addmod(1, 2, 2) == 1 assert c._num256_addmod(32, 2, 32) == 2 assert c._num256_addmod((2**256) - 1, 0, 2) == 1 assert_tx_failed(lambda: c._num256_addmod((2**256) - 1, 1, 1)) + assert_tx_failed(lambda: c._num256_addmod(1, 2, 0)) assert c._num256_mulmod(3, 1, 2) == 1 assert c._num256_mulmod(200, 3, 601) == 600 assert c._num256_mulmod(2**255, 1, 3) == 2 assert_tx_failed(lambda: c._num256_mulmod(2**255, 2, 1)) + assert_tx_failed(lambda: c._num256_mulmod(2, 2, 0)) def test_num256_with_exponents(t, chain, assert_tx_failed, get_contract_with_gas_estimation): diff --git a/viper/functions.py b/viper/functions.py index f09960f503..53174b38a3 100644 --- a/viper/functions.py +++ b/viper/functions.py @@ -691,12 +691,15 @@ def num256_exp(expr, args, kwargs, context): @signature('num256', 'num256') def num256_mod(expr, args, kwargs, context): - return LLLnode.from_list(['mod', args[0], args[1]], typ=BaseType('num256'), pos=getpos(expr)) + return LLLnode.from_list(['seq', + ['assert', args[1]], + ['mod', args[0], args[1]]], typ=BaseType('num256'), pos=getpos(expr)) @signature('num256', 'num256', 'num256') def num256_addmod(expr, args, kwargs, context): return LLLnode.from_list(['seq', + ['assert', args[2]], ['assert', ['or', ['iszero', args[1]], ['gt', ['add', args[0], args[1]], args[0]]]], ['addmod', args[0], args[1], args[2]]], typ=BaseType('num256'), pos=getpos(expr)) @@ -704,6 +707,7 @@ def num256_addmod(expr, args, kwargs, context): @signature('num256', 'num256', 'num256') def num256_mulmod(expr, args, kwargs, context): return LLLnode.from_list(['seq', + ['assert', args[2]], ['assert', ['or', ['iszero', args[0]], ['eq', ['div', ['mul', args[0], args[1]], args[0]], args[1]]]], ['mulmod', args[0], args[1], args[2]]], typ=BaseType('num256'), pos=getpos(expr)) From 3e2a3ed89a449c4a3ecc00746bd0dc63942c12c4 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Mon, 11 Dec 2017 19:34:46 +0200 Subject: [PATCH 136/162] Adds support for logging bytes from storage. --- tests/parser/features/test_logging.py | 23 +++++++++++++++++++++++ viper/parser/parser.py | 22 ++++++++++++++++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index 6fb2004bca..19142fca9a 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -590,3 +590,26 @@ def foo(): c.foo() assert get_last_log(t, c)["_value"] == [1.11, 2.22, 3.33, 4.44] + + +def test_storage_byte_packing(get_last_log, bytes_helper): + code = """ +MyLog: __log__({arg1: bytes <= 29}) +x:bytes<=5 + +@public +def foo(a:num): + log.MyLog(self.x) + +@public +def setbytez(): + self.x = 'hello' + """ + + c = get_contract_with_gas_estimation(code) + + c.foo() + assert get_last_log(t, c)['arg1'] == bytes_helper('', 29) + c.setbytez() + c.foo() + assert get_last_log(t, c)['arg1'] == bytes_helper('hello', 29) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 41ae80de5b..75b8ebd563 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -273,7 +273,7 @@ def __init__(self, vars=None, globals=None, sigs=None, forvars=None, return_type self.placeholder_count = 1 # Original code (for error pretty-printing purposes) self.origcode = origcode - # In Loop status. Wether body is currently evaluating within a for-loop or not. + # In Loop status. Whether body is currently evaluating within a for-loop or not. self.in_for_loop = set() def set_in_for_loop(self, name_of_list): @@ -364,7 +364,7 @@ def parse_other_functions(o, otherfuncs, _globals, sigs, external_contracts, ori def parse_tree_to_lll(code, origcode, runtime_only=False): _contracts, _events, _defs, _globals = get_contracts_and_defs_and_globals(code) _names = [_def.name for _def in _defs] + [_event.target.id for _event in _events] - # Checks for duplicate funciton / event names + # Checks for duplicate function / event names if len(set(_names)) < len(_names): raise VariableDeclarationException("Duplicate function or event name: %s" % [name for name in _names if _names.count(name) > 1][0]) # Initialization function @@ -681,8 +681,15 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): holder.append(LLLnode.from_list(['mstore', placeholder, value], typ=typ, location='memory')) elif isinstance(typ, ByteArrayType): bytez = b'' + # Bytes from Storage + if isinstance(arg, ast.Attribute) and arg.value.id == 'self': + stor_bytes = context.globals[arg.attr] + if stor_bytes.typ.maxlen > 32: + raise InvalidLiteralException("Can only log a maximum of 32 bytes at a time.") + arg2 = LLLnode.from_list(['sload', ['add', ['sha3_32', Expr(arg, context).lll_node], 1]], typ=BaseType(32)) + holder, maxlen = pack_args_by_32(holder, maxlen, arg2, BaseType(32), context, context.new_placeholder(BaseType(32))) # String literals - if isinstance(arg, ast.Str): + elif isinstance(arg, ast.Str): if len(arg.s) > typ.maxlen: raise TypeMismatchException("Data input bytes are to big: %r %r" % (len(arg.s), typ)) for c in arg.s: @@ -709,7 +716,8 @@ def check_list_type_match(provided): # Check list types match. "Log list type '%s' does not match provided, expected '%s'" % (provided, typ) ) - if isinstance(arg, ast.Attribute) and arg.value.id == 'self': # List from storage + # List from storage + if isinstance(arg, ast.Attribute) and arg.value.id == 'self': stor_list = context.globals[arg.attr] check_list_type_match(stor_list.typ.subtype) size = stor_list.typ.count @@ -717,7 +725,8 @@ def check_list_type_match(provided): # Check list types match. arg2 = LLLnode.from_list(['sload', ['add', ['sha3_32', Expr(arg, context).lll_node], offset]], typ=typ) holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32))) - elif isinstance(arg, ast.Name): # List from variable. + # List from variable. + elif isinstance(arg, ast.Name): size = context.vars[arg.id].size pos = context.vars[arg.id].pos check_list_type_match(context.vars[arg.id].typ.subtype) @@ -725,7 +734,8 @@ def check_list_type_match(provided): # Check list types match. offset = 32 * i arg2 = LLLnode.from_list(pos + offset, typ=typ, location='memory') holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32))) - else: # is list literal. + # is list literal. + else: holder, maxlen = pack_args_by_32(holder, maxlen, arg.elts[0], typ, context, placeholder) for j, arg2 in enumerate(arg.elts[1:]): holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder(BaseType(32))) From cbd0d0132cfca6055cea0cc7e928c05d43004b5a Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Mon, 11 Dec 2017 19:40:18 +0200 Subject: [PATCH 137/162] Adds exception to limit to 32 bytes of bytes type logging (restriction for the time being). --- tests/parser/syntax/test_logging.py | 18 ++++++++++++++++-- viper/parser/parser.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/parser/syntax/test_logging.py b/tests/parser/syntax/test_logging.py index 4b0f6c4c80..c860530082 100644 --- a/tests/parser/syntax/test_logging.py +++ b/tests/parser/syntax/test_logging.py @@ -21,6 +21,16 @@ def foo(): def foo(): x: decimal[4] log.Bar(x) + """, + """ +# larger than 32 bytes logging. + +MyLog: __log__({arg1: bytes <= 29}) +x:bytes<=55 + +@public +def foo(a:num): + log.MyLog(self.x) """ ] @@ -28,5 +38,9 @@ def foo(): @pytest.mark.parametrize('bad_code', fail_list) def test_logging_fail(bad_code): - with raises(TypeMismatchException): - compiler.compile(bad_code) + if isinstance(bad_code, tuple): + with raises(bad_code[1]): + compiler.compile(bad_code[0]) + else: + with raises(TypeMismatchException): + compiler.compile(bad_code) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 75b8ebd563..00a77114f0 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -685,7 +685,7 @@ def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder): if isinstance(arg, ast.Attribute) and arg.value.id == 'self': stor_bytes = context.globals[arg.attr] if stor_bytes.typ.maxlen > 32: - raise InvalidLiteralException("Can only log a maximum of 32 bytes at a time.") + raise TypeMismatchException("Can only log a maximum of 32 bytes at a time.") arg2 = LLLnode.from_list(['sload', ['add', ['sha3_32', Expr(arg, context).lll_node], 1]], typ=BaseType(32)) holder, maxlen = pack_args_by_32(holder, maxlen, arg2, BaseType(32), context, context.new_placeholder(BaseType(32))) # String literals From 4a45408076eb672728635139aa3faac17830232a Mon Sep 17 00:00:00 2001 From: James Ray Date: Tue, 12 Dec 2017 09:52:12 +1100 Subject: [PATCH 138/162] virtualenv -p /usr/local/lib/python3.6/bin/python3 > virtualenv -p python3.6 Related: https://github.com/ethereum/viper/issues/558 --- docs/installing-viper.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installing-viper.rst b/docs/installing-viper.rst index d9a1711482..dbaacf7369 100644 --- a/docs/installing-viper.rst +++ b/docs/installing-viper.rst @@ -75,7 +75,7 @@ other development environment set-up. To create a new virtual environment for Viper run the following commands: :: - virtualenv -p /usr/local/lib/python3.6/bin/python3 --no-site-packages ~/viper-venv + virtualenv -p python3.6 --no-site-packages ~/viper-venv source ~/viper-venv/bin/activate To find out more about virtual environments, check out: From 472942bf97aa975acade0f7272a4b921bf58dc86 Mon Sep 17 00:00:00 2001 From: James Ray Date: Tue, 12 Dec 2017 11:28:23 +1100 Subject: [PATCH 139/162] Add a note on how to fix `fatal error: openssl/aes.h: No such file or directory` in the `make` output --- docs/installing-viper.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/installing-viper.rst b/docs/installing-viper.rst index d9a1711482..80b47bd879 100644 --- a/docs/installing-viper.rst +++ b/docs/installing-viper.rst @@ -103,7 +103,9 @@ If everything works correctly, you are now able to compile your own smart contra However, please keep in mind that Viper is still experimental and not ready for production! .. note:: - For MacOS users: + If you get the error `fatal error: openssl/aes.h: No such file or directory` in the output of `make`, then run `sudo apt-get install libssl-dev1`, then run `make` again. + +   **For MacOS users:** Apple has deprecated use of OpenSSL in favor of its own TLS and crypto libraries. This means that you will need to export some OpenSSL settings From 975fcd7d2444131f1d2fe047fc4108e25fcedbb2 Mon Sep 17 00:00:00 2001 From: James Ray Date: Tue, 12 Dec 2017 14:53:05 +1100 Subject: [PATCH 140/162] removed extraneous backtick ` --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index c3965cf37f..f308905871 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,7 +48,7 @@ Following the principles and goals, Viper **does not** provide the following fea Compatibility-breaking Changelog ******************************** -* **2017.11.29**: ``@internal`` renamed to ``@private```. +* **2017.11.29**: ``@internal`` renamed to ``@private``. * **2017.11.15**: Functions require either ``@internal`` or ``@public`` decorators. * **2017.07.25**: The ``def foo() -> num(const): ...`` syntax no longer works; you now need to do ``def foo() -> num: ...`` with a ``@constant`` decorator on the previous line. * **2017.07.25**: Functions without a ``@payable`` decorator now fail when called with nonzero wei. From 4c21dc09b0923a6af855d1b4865921f59831d601 Mon Sep 17 00:00:00 2001 From: James Ray Date: Tue, 12 Dec 2017 15:39:02 +1100 Subject: [PATCH 141/162] Minor edits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit with ``num`` type > with the ``num`` type crowdfunding period is over - as determined> crowdfunding period is over—as determined --- docs/viper-by-example.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/viper-by-example.rst b/docs/viper-by-example.rst index 538235a098..cc04c088f5 100644 --- a/docs/viper-by-example.rst +++ b/docs/viper-by-example.rst @@ -272,9 +272,9 @@ This struct contains each participant's public address and their respective value contributed to the fund. The key corresponding to each struct in the mapping will be represented by the variable ``nextFunderIndex`` which is incremented with each additional contributing participant. Variables initialized -with ``num`` type without an explicit value, such as ``nextFunderIndex``, +with the ``num`` type without an explicit value, such as ``nextFunderIndex``, defaults to ``0``. The ``beneficiary`` will be the final receiver of the funds -once the crowdfunding period is over - as determined by the ``deadline`` and +once the crowdfunding period is over—as determined by the ``deadline`` and ``timelimit`` variables. The ``goal`` variable is the target total contribution of all participants. ``refundIndex`` is a variable for bookkeeping purposes in order to avoid gas limit issues in the scenario of a refund. From 30472cff20be75d6c0eb482e9f1f2f0c1eb51f4e Mon Sep 17 00:00:00 2001 From: James Ray Date: Tue, 12 Dec 2017 16:48:17 +1100 Subject: [PATCH 142/162] Minor edits voting add a comma > to vote, and each add a comma > to vote, or --- docs/viper-by-example.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/viper-by-example.rst b/docs/viper-by-example.rst index 538235a098..ce996e772f 100644 --- a/docs/viper-by-example.rst +++ b/docs/viper-by-example.rst @@ -346,7 +346,7 @@ Voting In this contract, we will implement a system for participants to vote on a list of proposals. The chairperson of the contract will be able to give each -participant the right to vote and each participant may choose to vote or +participant the right to vote, and each participant may choose to vote, or delegate their vote to another voter. Finally, a winning proposal will be determined upon calling the ``winning_proposals()`` method, which iterates through all the proposals and returns the one with the greatest number of votes. @@ -361,7 +361,7 @@ section by section. Let’s begin! .. literalinclude:: ../examples/voting/ballot.v.py :language: python - :lines: 3-21 + :lines: 3-25 The variable ``voters`` is initialized as a mapping where the key is the voter’s public address and the value is a struct describing the From 4390f26649be40e5099b60ecf75a848825d19a17 Mon Sep 17 00:00:00 2001 From: David Knott Date: Mon, 11 Dec 2017 02:40:08 +0100 Subject: [PATCH 143/162] Improve for loop error --- viper/parser/stmt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index a48322c8ff..b123d1dfd5 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -182,7 +182,7 @@ def parse_for(self): # Type 1 for, eg. for i in range(10): ... if len(self.stmt.iter.args) == 1: if not isinstance(self.stmt.iter.args[0], ast.Num): - raise StructureException("Repeat must have a nonzero positive integral number of rounds", self.stmt.iter) + raise StructureException("Range only accepts literal values", self.stmt.iter) start = LLLnode.from_list(0, typ='num', pos=getpos(self.stmt)) rounds = self.stmt.iter.args[0].n elif isinstance(self.stmt.iter.args[0], ast.Num) and isinstance(self.stmt.iter.args[1], ast.Num): @@ -197,7 +197,7 @@ def parse_for(self): if ast.dump(self.stmt.iter.args[0]) != ast.dump(self.stmt.iter.args[1].left): raise StructureException("Two-arg for statements of the form `for i in range(x, x + y): ...` must have x identical in both places: %r %r" % (ast.dump(self.stmt.iter.args[0]), ast.dump(self.stmt.iter.args[1].left)), self.stmt.iter) if not isinstance(self.stmt.iter.args[1].right, ast.Num): - raise StructureException("Repeat must have a nonzero positive integral number of rounds", self.stmt.iter.args[1]) + raise StructureException("Range only accepts literal values", self.stmt.iter.args[1]) start = Expr.parse_value_expr(self.stmt.iter.args[0], self.context) rounds = self.stmt.iter.args[1].right.n varname = self.stmt.target.id From 43df433542286579eca86e8be1e778549b32d5dc Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Wed, 13 Dec 2017 15:59:59 +0200 Subject: [PATCH 144/162] Adds test for logging a decimal list. --- tests/parser/features/test_logging.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index 5e9de1c084..2e232e695f 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -617,3 +617,26 @@ def setbytez(): c.setbytez() c.foo() assert get_last_log(t, c)['arg1'] == bytes_helper('hello', 29) + + +def test_storage_decimal_list_packing(t, get_last_log, bytes_helper, get_contract_with_gas_estimation, chain): + t.s = chain + code = """ +Bar: __log__({_value: decimal[4]}) +x: decimal[4] + +@public +def foo(): + log.Bar(self.x) + +@public +def set_list(): + self.x = [1.33, 2.33, 3.33, 4.33] + """ + c = get_contract_with_gas_estimation(code) + + c.foo() + assert get_last_log(t, c)["_value"] == [0, 0, 0, 0] + c.set_list() + c.foo() + assert get_last_log(t, c)["_value"] == [1.33, 2.33, 3.33, 4.33] From 1a8322cb163acf9a6f07aabfcd2ab58ec20b397b Mon Sep 17 00:00:00 2001 From: James Ray Date: Thu, 14 Dec 2017 13:32:53 +1100 Subject: [PATCH 145/162] :ref:`function-calls` > :ref:`Function-calls` --- docs/structure-of-a-contract.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index 4eebc78d47..0838a3f06b 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -39,7 +39,7 @@ Functions are the executable units of code within a contract. // ... } -:ref:`function-calls` can happen internally or externally +:ref:`Function-calls` can happen internally or externally and have different levels of visibility (:ref:`visibility-and-getters`) towards other contracts. Functions must be decorated with either @public or @private. From 1ef0475aa6db5f8006ba16fbccaf41335d100f1e Mon Sep 17 00:00:00 2001 From: James Ray Date: Thu, 14 Dec 2017 14:25:36 +1100 Subject: [PATCH 146/162] Minor edits types.rst --- docs/types.rst | 53 +++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 3e7ee92ac6..e459bd2bcd 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -73,10 +73,10 @@ Comparisons return a boolean value. Operator Description ========== ================ ``x < y`` Less than -``x <= y`` Less or equal +``x <= y`` Less than or equal to ``x == y`` Equals ``x != y`` Does not equal -``x >= y`` Greater or equal +``x >= y`` Greater than or equal to ``x > y`` Greater than ========== ================ ``x`` and ``y`` must be of the type ``num``. @@ -91,7 +91,7 @@ Operator Description ``x - y`` Subtraction ``-x`` Unary minus/Negation ``x * y`` Multiplication -``x / y`` Divison +``x / y`` Division ``x**y`` Exponentiation ``x % y`` Modulo ``min(x, y)`` Minimum @@ -108,7 +108,7 @@ An unsigned integer (256 bit) is a type to store non-negative integers. Values ------ -Integer values between 0 and (2\ :sup:`257`-1). +Integer values between 0 and (2\ :sup:`256`-1). .. note:: Integer literals are always interpreted as ``num``. In order to assign a literal to a ``num256`` use ``as_num256(_literal)``. @@ -123,10 +123,10 @@ Comparisons return a boolean value. Operator Description =================== ================ ``num256_lt(x, y)`` Less than -``num256_le(x, y)`` Less or equal +``num256_le(x, y)`` Less than or equal to ``x == y`` Equals ``x != y`` Does not equal -``num256_ge(x, y)`` Greater or equal +``num256_ge(x, y)`` Greater than or equal to ``num256_gt(x, y)`` Greater than =================== ================ ``x`` and ``y`` must be of the type ``num256``. @@ -223,7 +223,7 @@ The address type holds an Ethereum address. Values ------ -An address type can hold an Ethereum address which equates to 20 bytes/160 bits. Returns in hexadecimal notation with a leading ``0x``. +An address type can hold an Ethereum address which equates to 20 bytes or 160 bits. It returns in hexadecimal notation with a leading ``0x``. .. _members-of-addresses: Members @@ -232,7 +232,7 @@ Members ============ =================================================== Member Description ============ =================================================== -``balance`` Query balance of an address. Returns ``wei_value``. +``balance`` Query the balance of an address. Returns ``wei_value``. ``codesize`` Query the code size of an address. Returns ``num``. ============ =================================================== Syntax as follows: ``_address.``, where ``_address`` is of the type ``address`` and ```` is one of the above keywords. @@ -247,8 +247,8 @@ Time ----------------------------------------------------------- Keyword Unit Base type Description ============= ===== ========= ========================== -``timestamp`` 1 sec ``num`` Represents a point in time -``timedelta`` 1 sec ``num`` A number of seconds +``timestamp`` 1 sec ``num`` This represents a point in time. +``timedelta`` 1 sec ``num`` This is a number of seconds. ============= ===== ========= ========================== .. note:: @@ -259,17 +259,16 @@ Currency --------------------------------------------------------------------------------------------------------------------------------- Keyword Unit Base type Description =================== =========== ========= ==================================================================================== -``wei_value`` 1 wei ``num`` An amount of `Ether `_ in wei -``currency_value`` 1 currency ``num`` An amount of currency -``currency1_value`` 1 currency1 ``num`` An amount of currency1 -``currency2_value`` 1 currency2 ``num`` An amount of currency2 +``wei_value`` 1 wei ``num`` This is an amount of `Ether `_ in wei. +``currency1_value`` 1 currency1 ``num`` This is an amount of currency1. +``currency2_value`` 1 currency2 ``num`` This is an amount of currency2. =================== =========== ========= ==================================================================================== .. index:: !bytes32 32-bit-wide Byte Array ====================== **Keyword:** ``bytes32`` -A 32-bit-wide byte array. Otherwise similiar to byte arrays. +This is a 32-bit-wide byte array that is otherwise similiar to byte arrays. **Example:** :: @@ -282,10 +281,10 @@ Operators ==================================== ============================================================ Keyword Description ==================================== ============================================================ -``len(x)`` Returns the length as an integer -``sha3(x)`` Returns the sha3 hash as bytes32 -``concat(x, ...)`` Concatenates multiple inputs -``slice(x, start=_start, len=_len)`` Returns a slice of ``_len`` starting at ``_start`` +``len(x)`` Return the length as an integer. +``sha3(x)`` Return the sha3 hash as bytes32. +``concat(x, ...)`` Concatenate multiple inputs. +``slice(x, start=_start, len=_len)`` Return a slice of ``_len`` starting at ``_start``. ==================================== ============================================================ Where ``x`` is a byte array and ``_start`` as well as ``_len`` are integer values. @@ -311,12 +310,12 @@ Operators ==================================== ============================================================ Keyword Description ==================================== ============================================================ -``len(x)`` Returns the length as an integer -``sha3(x)`` Returns the sha3 hash as bytes32 -``concat(x, ...)`` Concatenates multiple inputs -``slice(x, start=_start, len=_len)`` Returns a slice of ``_len`` starting at ``_start`` +``len(x)`` Return the length as an integer. +``sha3(x)`` Return the sha3 hash as bytes32. +``concat(x, ...)`` Concatenate multiple inputs. +``slice(x, start=_start, len=_len)`` Return a slice of ``_len`` starting at ``_start``. ==================================== ============================================================ -Where ``x`` is a byte array and ``_start`` as well as ``_len`` are integer values. +Where ``x`` is a byte array while ``_start`` and ``_len`` are integers. .. index:: !reference @@ -324,8 +323,8 @@ Where ``x`` is a byte array and ``_start`` as well as ``_len`` are integer value Reference Types *************** -Reference types do not fit into 32 Bytes. Because of this, copying their value is not as feasible as -with value types. Therefore only the location, the reference, of the data is passed. +Reference types do not fit into 32 bytes. Because of this, copying their value is not as feasible as +with value types. Therefore only the location, i.e. the reference, of the data is passed. .. index:: !arrays Fixed-size Lists @@ -407,7 +406,7 @@ Here ``_KeyType`` can be almost any type except for mappings, a contract, or a s ********** Conversion ********** -Following conversions are possible. +The following conversions are possible. =================== ===================================================================================================================== ============= Keyword Input Output From 8f070531bf51ff69558b0d5950b450ee18af0038 Mon Sep 17 00:00:00 2001 From: David Knott Date: Thu, 7 Dec 2017 23:48:05 +0100 Subject: [PATCH 147/162] Add length check to event topics --- tests/parser/features/test_logging.py | 18 ++++++++++++++++++ viper/signatures/event_signature.py | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index 2e232e695f..c0594f0c17 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -640,3 +640,21 @@ def set_list(): c.set_list() c.foo() assert get_last_log(t, c)["_value"] == [1.33, 2.33, 3.33, 4.33] + + +def test_logging_fails_when_declartation_is_too_big(assert_tx_failed): + code = """ +Bar: __log__({_value: indexed(bytes <= 33)}) +""" + assert_tx_failed(lambda: get_contract_with_gas_estimation(code), VariableDeclarationException) + + +def test_logging_fails_when_input_is_too_big(assert_tx_failed): + code = """ +Bar: __log__({_value: indexed(bytes <= 32)}) + +@public +def foo(inp: bytes <= 33): + log.Bar(inp) +""" + assert_tx_failed(lambda: get_contract_with_gas_estimation(code), TypeMismatchException) diff --git a/viper/signatures/event_signature.py b/viper/signatures/event_signature.py index f32913ce70..78d88cab3b 100644 --- a/viper/signatures/event_signature.py +++ b/viper/signatures/event_signature.py @@ -39,9 +39,9 @@ def from_declaration(cls, code): else: raise VariableDeclarationException("Only indexed keyword is allowed", arg) else: - if hasattr(typ, 'left') and typ.left.id == 'bytes' and typ.comparators[0].n > 32: - raise VariableDeclarationException("Can only log a maximum of 32 bytes at a time.") indexed_list.append(False) + if hasattr(typ, 'left') and typ.left.id == 'bytes' and typ.comparators[0].n > 32: + raise VariableDeclarationException("Can only log a maximum of 32 bytes at a time.") if topics_count > 4: raise VariableDeclarationException("Maximum of 3 topics {} given".format(topics_count - 1), arg) if not isinstance(arg, str): From 83b90f34b5e89de9e2e4dde40f667099ad51bb20 Mon Sep 17 00:00:00 2001 From: David Knott Date: Fri, 15 Dec 2017 01:31:37 +0100 Subject: [PATCH 148/162] Fix and test logging with units --- tests/parser/features/test_logging.py | 14 ++++++++++++-- viper/signatures/event_signature.py | 13 +++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index c0594f0c17..916a66adcd 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -131,6 +131,16 @@ def foo(): assert c.translator.decode_event(logs.topics, logs.data) == {'arg1': 123, '_event_type': b'MyLog'} +def test_event_logging_with_units(get_contract_with_gas_estimation, chain, utils): + code = """ +MyLog: __log__({arg1: indexed(num(wei)), arg2: num(wei)}) + +@public +def foo(): + log.MyLog(1, 2) +""" + get_contract_with_gas_estimation(code) + def test_event_loggging_with_fixed_array_data(get_contract_with_gas_estimation, chain, utils): loggy_code = """ MyLog: __log__({arg1: num[2], arg2: timestamp[3], arg3: num[2][2]}) @@ -642,14 +652,14 @@ def set_list(): assert get_last_log(t, c)["_value"] == [1.33, 2.33, 3.33, 4.33] -def test_logging_fails_when_declartation_is_too_big(assert_tx_failed): +def test_logging_fails_when_declartation_is_too_big(assert_tx_failed, get_contract_with_gas_estimation): code = """ Bar: __log__({_value: indexed(bytes <= 33)}) """ assert_tx_failed(lambda: get_contract_with_gas_estimation(code), VariableDeclarationException) -def test_logging_fails_when_input_is_too_big(assert_tx_failed): +def test_logging_fails_when_input_is_too_big(assert_tx_failed, get_contract_with_gas_estimation): code = """ Bar: __log__({_value: indexed(bytes <= 32)}) diff --git a/viper/signatures/event_signature.py b/viper/signatures/event_signature.py index 78d88cab3b..6e0c621e52 100644 --- a/viper/signatures/event_signature.py +++ b/viper/signatures/event_signature.py @@ -30,14 +30,11 @@ def from_declaration(cls, code): for i in range(len(keys)): typ = values[i] arg = keys[i].id - if isinstance(typ, ast.Call): - # Check to see if argument is a topic - if typ.func.id == 'indexed': - typ = values[i].args[0] - indexed_list.append(True) - topics_count += 1 - else: - raise VariableDeclarationException("Only indexed keyword is allowed", arg) + # Check to see if argument is a topic + if isinstance(typ, ast.Call) and typ.func.id == 'indexed': + typ = values[i].args[0] + indexed_list.append(True) + topics_count += 1 else: indexed_list.append(False) if hasattr(typ, 'left') and typ.left.id == 'bytes' and typ.comparators[0].n > 32: From 080e4df4cd9173e0bea3b0679cca3122d16d0ff1 Mon Sep 17 00:00:00 2001 From: David Knott Date: Fri, 15 Dec 2017 01:31:53 +0100 Subject: [PATCH 149/162] Improve type parsing error message --- viper/types.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/viper/types.py b/viper/types.py index 731799697e..bde655645c 100644 --- a/viper/types.py +++ b/viper/types.py @@ -262,7 +262,8 @@ def parse_type(item, location, sigs={}): raise InvalidTypeException("Malformed unit type:", item) base_type = item.func.id if base_type not in ('num', 'decimal'): - raise InvalidTypeException("Base type with units can only be num, decimal", item) + raise InvalidTypeException("You must use num, decimal, address, contract, \ + for variable declarations and indexed for logging topics ", item) if len(item.args) == 0: raise InvalidTypeException("Malformed unit type", item) if isinstance(item.args[-1], ast.Name) and item.args[-1].id == "positional": From d16ea6d48682506f5b8ef3ee5c606ebe5e59be7d Mon Sep 17 00:00:00 2001 From: David Knott Date: Fri, 15 Dec 2017 01:32:21 +0100 Subject: [PATCH 150/162] Add valid call keyword list to utils --- viper/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/viper/utils.py b/viper/utils.py index 7002ab151b..f8feb2c1e6 100644 --- a/viper/utils.py +++ b/viper/utils.py @@ -113,6 +113,9 @@ class SizeLimits: # Available base types base_types = ['num', 'decimal', 'bytes32', 'num256', 'signed256', 'bool', 'address'] +# Keywords available for ast.Call type +valid_call_keywords = ['num', 'decimal', 'address', 'contract', 'indexed'] + # Valid base units valid_units = ['currency', 'wei', 'currency1', 'currency2', 'sec', 'm', 'kg'] From 43c8ef606cb31482688a1a166649c812e6d2a8d8 Mon Sep 17 00:00:00 2001 From: David Knott Date: Thu, 7 Dec 2017 23:48:05 +0100 Subject: [PATCH 151/162] Add length check to event topics --- tests/parser/features/test_logging.py | 18 ++++++++++++++++++ viper/signatures/event_signature.py | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/parser/features/test_logging.py b/tests/parser/features/test_logging.py index 2e232e695f..88b053b6b9 100644 --- a/tests/parser/features/test_logging.py +++ b/tests/parser/features/test_logging.py @@ -640,3 +640,21 @@ def set_list(): c.set_list() c.foo() assert get_last_log(t, c)["_value"] == [1.33, 2.33, 3.33, 4.33] + + +def test_logging_fails_when_declartation_is_too_big(assert_tx_failed, get_contract_with_gas_estimation): + code = """ +Bar: __log__({_value: indexed(bytes <= 33)}) +""" + assert_tx_failed(lambda: get_contract_with_gas_estimation(code), VariableDeclarationException) + + +def test_logging_fails_when_input_is_too_big(assert_tx_failed, get_contract_with_gas_estimation): + code = """ +Bar: __log__({_value: indexed(bytes <= 32)}) + +@public +def foo(inp: bytes <= 33): + log.Bar(inp) +""" + assert_tx_failed(lambda: get_contract_with_gas_estimation(code), TypeMismatchException) diff --git a/viper/signatures/event_signature.py b/viper/signatures/event_signature.py index f32913ce70..78d88cab3b 100644 --- a/viper/signatures/event_signature.py +++ b/viper/signatures/event_signature.py @@ -39,9 +39,9 @@ def from_declaration(cls, code): else: raise VariableDeclarationException("Only indexed keyword is allowed", arg) else: - if hasattr(typ, 'left') and typ.left.id == 'bytes' and typ.comparators[0].n > 32: - raise VariableDeclarationException("Can only log a maximum of 32 bytes at a time.") indexed_list.append(False) + if hasattr(typ, 'left') and typ.left.id == 'bytes' and typ.comparators[0].n > 32: + raise VariableDeclarationException("Can only log a maximum of 32 bytes at a time.") if topics_count > 4: raise VariableDeclarationException("Maximum of 3 topics {} given".format(topics_count - 1), arg) if not isinstance(arg, str): From c5a3b22b32050bd0c57ae31c4f45dab662d288d1 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Tue, 19 Dec 2017 14:29:00 +0200 Subject: [PATCH 152/162] Partial fix for #590. Adds check for a minimum of one return statement. --- viper/parser/parser.py | 12 ++++++++++++ viper/parser/stmt.py | 1 + 2 files changed, 13 insertions(+) diff --git a/viper/parser/parser.py b/viper/parser/parser.py index 00a77114f0..4e58e9e8d4 100644 --- a/viper/parser/parser.py +++ b/viper/parser/parser.py @@ -275,6 +275,8 @@ def __init__(self, vars=None, globals=None, sigs=None, forvars=None, return_type self.origcode = origcode # In Loop status. Whether body is currently evaluating within a for-loop or not. self.in_for_loop = set() + # Count returns in function + self.function_return_count = 0 def set_in_for_loop(self, name_of_list): self.in_for_loop.add(name_of_list) @@ -282,6 +284,9 @@ def set_in_for_loop(self, name_of_list): def remove_in_for_loop(self, name_of_list): self.in_for_loop.remove(name_of_list) + def increment_return_counter(self): + self.function_return_count += 1 + # Add a new variable def new_variable(self, name, typ): if not is_varname_valid(name): @@ -470,6 +475,13 @@ def parse_func(code, _globals, sigs, origcode, _vars=None): ['eq', ['mload', 0], method_id_node], ['seq'] + clampers + [parse_body(c, context) for c in code.body] + ['stop'] ], typ=None, pos=getpos(code)) + + # Check for at leasts one return statement if necessary. + if context.return_type and context.function_return_count == 0: + raise StructureException( + "Missing return statement in function '%s' " % sig.name, code + ) + o.context = context o.total_gas = o.gas + calc_mem_gas(o.context.next_mem) o.func_name = sig.name diff --git a/viper/parser/stmt.py b/viper/parser/stmt.py index b123d1dfd5..aedced1961 100644 --- a/viper/parser/stmt.py +++ b/viper/parser/stmt.py @@ -320,6 +320,7 @@ def parse_return(self): if not self.stmt.value: raise TypeMismatchException("Expecting to return a value", self.stmt) sub = Expr(self.stmt.value, self.context).lll_node + self.context.increment_return_counter() # Returning a value (most common case) if isinstance(sub.typ, BaseType): if not isinstance(self.context.return_type, BaseType): From 447112a74a21570e341010a9e2d6fd3a5b270759 Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Tue, 19 Dec 2017 14:33:54 +0200 Subject: [PATCH 153/162] Fixes test that were missing return statements. --- .../parser/exceptions/test_invalid_payable.py | 1 + .../test_external_contract_calls.py | 2 +- tests/parser/syntax/test_invalids.py | 4 ++-- tests/parser/syntax/test_list.py | 14 ++++++------- tests/parser/syntax/test_missing_return.py | 20 +++++++++++++++++++ 5 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 tests/parser/syntax/test_missing_return.py diff --git a/tests/parser/exceptions/test_invalid_payable.py b/tests/parser/exceptions/test_invalid_payable.py index 6a082b5055..0a27bb1b7e 100644 --- a/tests/parser/exceptions/test_invalid_payable.py +++ b/tests/parser/exceptions/test_invalid_payable.py @@ -27,6 +27,7 @@ def test_variable_decleration_exception(bad_code): @payable def foo() -> num: self.x = 5 + return self.x """, """ @public diff --git a/tests/parser/features/external_contracts/test_external_contract_calls.py b/tests/parser/features/external_contracts/test_external_contract_calls.py index 6e6cee7793..781365978a 100644 --- a/tests/parser/features/external_contracts/test_external_contract_calls.py +++ b/tests/parser/features/external_contracts/test_external_contract_calls.py @@ -495,7 +495,7 @@ def bar() -> num: pass bar_contract: public(Bar) @public -def foo(contract_address: contract(Bar)) -> num: +def foo(contract_address: contract(Bar)): self.bar_contract = contract_address @public diff --git a/tests/parser/syntax/test_invalids.py b/tests/parser/syntax/test_invalids.py index 3ce25fda63..313e42dbf0 100644 --- a/tests/parser/syntax/test_invalids.py +++ b/tests/parser/syntax/test_invalids.py @@ -154,14 +154,14 @@ def foo(): must_succeed(""" x: num @public -def foo() -> num: +def foo(): self.x = 5 """) must_succeed(""" x: num @private -def foo() -> num: +def foo(): self.x = 5 """) diff --git a/tests/parser/syntax/test_list.py b/tests/parser/syntax/test_list.py index a13547fcfb..6624abb9ba 100644 --- a/tests/parser/syntax/test_list.py +++ b/tests/parser/syntax/test_list.py @@ -32,21 +32,21 @@ def foo() -> num[2]: y: num[3] @public -def foo(x: num[3]) -> num: +def foo(x: num[3]): self.y = x[0] """, """ y: num[3] @public -def foo(x: num[3]) -> num: +def foo(x: num[3]): self.y[0] = x """, """ y: num[4] @public -def foo(x: num[3]) -> num: +def foo(x: num[3]): self.y = x """, """ @@ -168,28 +168,28 @@ def foo(x: num[3]) -> num: y: num[3] @public -def foo(x: num[3]) -> num: +def foo(x: num[3]): self.y = x """, """ y: decimal[3] @public -def foo(x: num[3]) -> num: +def foo(x: num[3]): self.y = x """, """ y: decimal[2][2] @public -def foo(x: num[2][2]) -> num: +def foo(x: num[2][2]): self.y = x """, """ y: decimal[2] @public -def foo(x: num[2][2]) -> num: +def foo(x: num[2][2]): self.y = x[1] """, """ diff --git a/tests/parser/syntax/test_missing_return.py b/tests/parser/syntax/test_missing_return.py new file mode 100644 index 0000000000..963768687c --- /dev/null +++ b/tests/parser/syntax/test_missing_return.py @@ -0,0 +1,20 @@ +import pytest +from pytest import raises + +from viper import compiler +from viper.exceptions import StructureException + + +fail_list = [ + """ +@public +def foo() -> num: + pass + """, +] + + +@pytest.mark.parametrize('bad_code', fail_list) +def test_missing_return(bad_code): + with raises(StructureException): + compiler.compile(bad_code) From d898f0cc14fb224101b170deeba39efb6be7336b Mon Sep 17 00:00:00 2001 From: Jacques Wagener Date: Wed, 20 Dec 2017 15:38:49 +0200 Subject: [PATCH 154/162] Adds example of return checker behaving incorrectly. --- tests/parser/syntax/test_missing_return.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/parser/syntax/test_missing_return.py b/tests/parser/syntax/test_missing_return.py index 963768687c..e7d7440ccb 100644 --- a/tests/parser/syntax/test_missing_return.py +++ b/tests/parser/syntax/test_missing_return.py @@ -18,3 +18,23 @@ def foo() -> num: def test_missing_return(bad_code): with raises(StructureException): compiler.compile(bad_code) + + + +valid_list = [ + """ +@public +def foo() -> num: + return 123 + """, + """ +@public +def foo() -> num: + if false: + return 123 + """, # For the time being this is valid code, even though it should not be. +] + +@pytest.mark.parametrize('good_code', valid_list) +def test_return_success(good_code): + assert compiler.compile(good_code) is not None From a526598e8064deaaabbec6266d8b104855147123 Mon Sep 17 00:00:00 2001 From: Mark Beacom Date: Fri, 22 Dec 2017 01:44:21 -0500 Subject: [PATCH 155/162] Update dockerfile to use slim image base --- Dockerfile | 26 ++++++++++++++++++++------ Makefile | 5 +++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 25a22b30c5..125aa06168 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,27 @@ -FROM python:3.6 +FROM python:3.6-slim -MAINTAINER Obul +# Specify label-schema specific arguments and labels. +ARG BUILD_DATE +ARG VCS_REF +LABEL org.label-schema.build-date=$BUILD_DATE \ + org.label-schema.name="viper" \ + org.label-schema.description="Viper is an experimental programming language" \ + org.label-schema.url="https://viper.readthedocs.io/en/latest/" \ + org.label-schema.vcs-ref=$VCS_REF \ + org.label-schema.vcs-url="https://github.com/ethereum/viper" \ + org.label-schema.vendor="Ethereum" \ + org.label-schema.schema-version="1.0" # coincurve requires libgmp RUN apt-get update && \ - apt-get install -y libgmp-dev + apt-get install -y --no-install-recommends apt-utils gcc libc6-dev libc-dev libssl-dev libgmp-dev && \ + rm -rf /var/lib/apt/lists/* + +ADD . /code # download and install Viper +# WORKDIR /code +# RUN git clone https://github.com/ethereum/viper.git WORKDIR /code -RUN git clone https://github.com/ethereum/viper.git -WORKDIR /code/viper -RUN python setup.py install +RUN python setup.py install && \ + apt-get purge -y --auto-remove apt-utils gcc libc6-dev libc-dev libssl-dev diff --git a/Makefile b/Makefile index c41214cc63..cabd0dd338 100644 --- a/Makefile +++ b/Makefile @@ -25,3 +25,8 @@ docs: $(MAKE) -C docs clean $(MAKE) -C docs html open docs/_build/html/index.html + +docker-build: + @docker build \ + --build-arg VCS_REF=`git rev-parse --short HEAD` \ + --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` . From 48c67c86bc11c3d91914234dbb085c4a736bd68b Mon Sep 17 00:00:00 2001 From: Mark Beacom Date: Fri, 22 Dec 2017 01:52:29 -0500 Subject: [PATCH 156/162] Remove commented out directives --- Dockerfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 125aa06168..b626525f2a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,9 +19,6 @@ RUN apt-get update && \ ADD . /code -# download and install Viper -# WORKDIR /code -# RUN git clone https://github.com/ethereum/viper.git WORKDIR /code RUN python setup.py install && \ apt-get purge -y --auto-remove apt-utils gcc libc6-dev libc-dev libssl-dev From 33f96d426469c215d34fb7e75408b0204d804a1f Mon Sep 17 00:00:00 2001 From: David Knott Date: Fri, 22 Dec 2017 21:50:28 +0700 Subject: [PATCH 157/162] Reduce num256_add gas cost --- viper/functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/viper/functions.py b/viper/functions.py index f09960f503..e2cb87160d 100644 --- a/viper/functions.py +++ b/viper/functions.py @@ -651,8 +651,8 @@ def bitwise_xor(expr, args, kwargs, context): @signature('num256', 'num256') def num256_add(expr, args, kwargs, context): return LLLnode.from_list(['seq', - # Checks that: a + b > a - ['assert', ['or', ['iszero', args[1]], ['gt', ['add', args[0], args[1]], args[0]]]], + # Checks that: a + b >= a + ['assert', ['ge', ['add', args[0], args[1]], args[0]]], ['add', args[0], args[1]]], typ=BaseType('num256'), pos=getpos(expr)) From 6e796889fb5db78aa9c5f6496892b6fe971046de Mon Sep 17 00:00:00 2001 From: Mark Beacom Date: Sat, 23 Dec 2017 15:03:05 -0500 Subject: [PATCH 158/162] Tag built image as viper --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index cabd0dd338..4ec146f8bc 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,6 @@ docs: open docs/_build/html/index.html docker-build: - @docker build \ + @docker build -t viper \ --build-arg VCS_REF=`git rev-parse --short HEAD` \ - --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` . + --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` . \ No newline at end of file From 9a218f7d8816676beb54195a3212d7ea5aaaec82 Mon Sep 17 00:00:00 2001 From: David Knott Date: Fri, 22 Dec 2017 19:24:35 +0700 Subject: [PATCH 159/162] Add built-in function docs --- docs/built-in-functions.rst | 230 ++++++++++++++++++++++++++++++++++++ docs/viper-in-depth.rst | 1 + 2 files changed, 231 insertions(+) create mode 100644 docs/built-in-functions.rst diff --git a/docs/built-in-functions.rst b/docs/built-in-functions.rst new file mode 100644 index 0000000000..c0bfaf0c1b --- /dev/null +++ b/docs/built-in-functions.rst @@ -0,0 +1,230 @@ +.. index:: function, built-in; + +.. _built_in_functions: + +*********************** +Built in Functions +*********************** + +Viper contains a set amount of built in functions that would be timely and/or unachievable to write in Viper. + +.. _functions: + +Functions +========= +* **floor** +:: + + def floor(a) -> b: + """ + :param a: value to round down + :type a: either decimal or num + + :output b: integer + """ +Rounds a decimal down to the nearest integer. + +* **decimal** +:: + + def decimal(a) -> b: + """ + :param a: value to turn into decimal + :type a: either decimal or num + + :output b: decimal + """ +Turns a number into a decimal. + +* **as_unitless_number** +:: + + def as_unitless_number(a) -> b: + """ + :param a: value to remove units from + :type a: either decimal or num + + :output b: either decimal or num + """ +Turns a ``num`` or ``decimal`` with units into one without units (used for assignment and math). + +* **as_num128** +:: + + def as_num128(a) -> b: + """ + :param a: value to turn into int128 + :type a: either num, bytes32, num256, or bytes + + :output b: num + """ +Turns input into a ``num`` (int128). + +* **as_num256** +:: + + def as_num256(a) -> b: + """ + :param a: value to turn into num256 + :type a: either num_literal, num, bytes32, or address + + :output b: num256 + """ +Turns input into a ``num256`` (uint256). + +* **as_bytes32** +:: + + def as_bytes32(a) -> b: + """ + :param a: value to turn into bytes32 + :type a: either num, num256, address + + :output b: bytes32 + """ +Turns input into a ``bytes32``. +* **slice** +:: + + def slice(a, start=b, length=c) -> d: + """ + :param a: bytes to be sliced + :type a: either bytes or bytes32 + :param b: start position of the slice + :type b: num + :param c: length of the slice + :type c: num + + :output d: bytes + """ +Takes a list of bytes and copies, then returns a specified chunk. + +* **len** +:: + + def len(a) -> b: + """ + :param a: value to get the length of + :type a: bytes + + :output b: num + """ +Returns the length of a given list of bytes. + +* **concat** +:: + + def concat(a, b, ...) -> c: + """ + :param a: value to combine + :type a: bytes + :param b: value to combine + :type b: bytes + + :output b: bytes + """ +Takes 2 or more bytes arrays of type ``bytes32`` or ``bytes`` and combines them into one. + +* **keccak256 (sha3)** +:: + + def keccak256(a) -> b: + """ + :param a: value to hash + :type a: either str_literal, bytes, bytes32 + + :output b: bytes32 + """ +Returns ``keccak_256`` (Ethereums sha3) hash of input. + +* **method_id** +:: + + def method_id(a) -> b: + """ + :param a: method declaration + :type a: str_literal + + :output b: bytes + """ + +Takes a function declaration and returns its method_id (used in data field to call it). + +* **ecrecover** +:: + + def ecrecover(hash, v, r, s) -> b: + """ + :param hash: a signed hash + :type hash: bytes32 + :param v: + :type v: num256 + :param r: elliptic curve point + :type r: num256 + :param s: elliptic curve point + :type s: num256 + + :output b: address + """ + +Takes a signed hash and vrs and returns the public key of the signer. + +* **ecadd** +:: + + def ecadd(a, b) -> sum: + """ + :param a: pair to be added + :type a: num252[2] + :param b: pair to be added + :type b: num252[2] + + :output sum: num256[2] + """ + +Takes two elliptical curves and adds them together. + +* **ecmul** +:: + + def ecmul(a, b) -> product: + """ + :param a: pair to be multiplied + :type a: num252[2] + :param b: pair to be multiplied + :type b: num252[2] + + :output product: num256[2] + """ + +Takes two elliptical curves and multiplies them together. + +* **extract32** +:: + + def extract32(a, b, type=c) -> d: + """ + :param a: where 32 bytes are extracted from + :type a: bytes + :param b: start point of bytes to be extracted + :type b: num + :param c: type of output + :type c: either bytes32, num128, or address + + :output d: either bytes32, num128, or address + """ + +Takes a byte array and extracts 32 bytes from it. + +* **bytes_to_num** +:: + + def bytes_to_num(a) -> b: + """ + :param a: bytes to be transformed + :type a: bytes + + :output d: num + """ + +Transforms bytes to num. diff --git a/docs/viper-in-depth.rst b/docs/viper-in-depth.rst index 8401d672fa..5df8f1fed0 100644 --- a/docs/viper-in-depth.rst +++ b/docs/viper-in-depth.rst @@ -16,6 +16,7 @@ If something is missing here, please contact us on grammar.rst units-and-global-variables.rst control-structures.rst + built-in-functions.rst contracts.rst assembly.rst miscellaneous.rst From d6c60bdc53920419d80d79fa7d017dd8ba053729 Mon Sep 17 00:00:00 2001 From: David Knott Date: Wed, 20 Dec 2017 16:55:19 +0700 Subject: [PATCH 160/162] Fix on chain market maker --- .../market_maker/on_chain_market_maker.v.py | 14 ++++++----- .../test_on_chain_market_maker.py | 24 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/examples/market_maker/on_chain_market_maker.v.py b/examples/market_maker/on_chain_market_maker.v.py index b81f014c9d..c5150bd00a 100644 --- a/examples/market_maker/on_chain_market_maker.v.py +++ b/examples/market_maker/on_chain_market_maker.v.py @@ -1,19 +1,19 @@ total_eth_qty: public(wei_value) -total_token_qty: public(num) -invariant: public(wei_value) +total_token_qty: public(currency_value) +invariant: public(num(wei * currency)) token_address: address(ERC20) owner: public(address) @public @payable -def initiate(token_addr: address, token_quantity: num): +def initiate(token_addr: address, token_quantity: currency_value): assert self.invariant == 0 self.token_address = token_addr self.token_address.transferFrom(msg.sender, self, as_num256(token_quantity)) self.owner = msg.sender self.total_eth_qty = msg.value self.total_token_qty = token_quantity - self.invariant = msg.value + self.invariant = msg.value * token_quantity assert self.invariant > 0 @public @@ -25,16 +25,18 @@ def eth_to_tokens(): new_total_tokens = self.invariant / new_total_eth self.token_address.transfer(msg.sender, as_num256(self.total_token_qty - new_total_tokens)) + self.total_eth_qty = new_total_eth self.total_token_qty = new_total_tokens @public -def tokens_to_eth(sell_quantity: num): - # self.token_address.transferFrom(msg.sender, self, as_num256(sell_quantity)) +def tokens_to_eth(sell_quantity: currency_value): + self.token_address.transferFrom(msg.sender, self, as_num256(sell_quantity)) new_total_tokens = self.total_token_qty + sell_quantity new_total_eth = self.invariant / new_total_tokens eth_to_send = self.total_eth_qty - new_total_eth send(msg.sender, eth_to_send) self.total_eth_qty = new_total_eth + self.total_token_qty = new_total_tokens @public def owner_withdraw(): diff --git a/tests/examples/market_maker/test_on_chain_market_maker.py b/tests/examples/market_maker/test_on_chain_market_maker.py index 4a67343dfb..b8353fd278 100644 --- a/tests/examples/market_maker/test_on_chain_market_maker.py +++ b/tests/examples/market_maker/test_on_chain_market_maker.py @@ -32,7 +32,7 @@ def test_initiate(t, chain, utils, market_maker, erc20, assert_tx_failed): market_maker.initiate(erc20.address, 1*10**18, value=2*10**18) assert market_maker.get_total_eth_qty() == 2*10**18 assert market_maker.get_total_token_qty() == 1*10**18 - assert market_maker.get_invariant() == 2*10**18 + assert market_maker.get_invariant() == 2*10**36 assert utils.remove_0x_head(market_maker.get_owner()) == t.a0.hex() t.s = chain # Initiate cannot be called twice @@ -44,22 +44,28 @@ def test_eth_to_tokens(t, market_maker, erc20): market_maker.initiate(erc20.address, 1*10**18, value=2*10**18) assert erc20.balanceOf(market_maker.address) == 1000000000000000000 assert erc20.balanceOf(t.a1) == 0 + assert market_maker.get_total_token_qty() == 1000000000000000000 + assert market_maker.get_total_eth_qty() == 2000000000000000000 market_maker.eth_to_tokens(value=100, sender=t.k1) - assert erc20.balanceOf(market_maker.address) == 0 - assert erc20.balanceOf(t.a1) == 1000000000000000000 - assert market_maker.get_total_token_qty() == 0 + assert erc20.balanceOf(market_maker.address) == 999999999999999950 + assert erc20.balanceOf(t.a1) == 50 + assert market_maker.get_total_token_qty() == 999999999999999950 + assert market_maker.get_total_eth_qty() == 2000000000000000100 def test_tokens_to_eth(t, chain, market_maker, erc20): + erc20.transfer(t.a1, 1 * 10** 18) erc20.approve(market_maker.address, 2*10**18) market_maker.initiate(erc20.address, 1*10**18, value=2*10**18) assert chain.head_state.get_balance(market_maker.address) == 2000000000000000000 - assert chain.head_state.get_balance(t.a1) == 999999999999999999999900 + assert chain.head_state.get_balance(t.a1) == 999999999999999999999900 + assert market_maker.get_total_token_qty() == 1000000000000000000 erc20.approve(market_maker.address, 1*10**18, sender=t.k1) - market_maker.tokens_to_eth(100, sender=t.k1) - assert chain.head_state.get_balance(market_maker.address) == 1 - assert chain.head_state.get_balance(t.a1) == 1000001999999999999999899 - assert market_maker.get_total_eth_qty() == 1 + market_maker.tokens_to_eth(1*10**18, sender=t.k1) + assert chain.head_state.get_balance(market_maker.address) == 1000000000000000000 + assert chain.head_state.get_balance(t.a1) == 1000000999999999999999900 + assert market_maker.get_total_token_qty() == 2000000000000000000 + assert market_maker.get_total_eth_qty() == 1000000000000000000 def test_owner_withdraw(t, chain, market_maker, erc20, assert_tx_failed): From 2c201bedb6768e5c0042946efdd72086bbf68d4d Mon Sep 17 00:00:00 2001 From: David Knott Date: Thu, 21 Dec 2017 13:00:37 +0700 Subject: [PATCH 161/162] Add comments to on chain market maker --- examples/market_maker/on_chain_market_maker.v.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/market_maker/on_chain_market_maker.v.py b/examples/market_maker/on_chain_market_maker.v.py index c5150bd00a..69adb1a200 100644 --- a/examples/market_maker/on_chain_market_maker.v.py +++ b/examples/market_maker/on_chain_market_maker.v.py @@ -1,9 +1,13 @@ total_eth_qty: public(wei_value) total_token_qty: public(currency_value) +# Constant set in `initiate` that's used to calculate +# the amount of ether/tokens that are exchanged invariant: public(num(wei * currency)) token_address: address(ERC20) owner: public(address) +# Sets the on chain market maker with its owner, intial token quantity, +# and initial ether quantity @public @payable def initiate(token_addr: address, token_quantity: currency_value): @@ -16,6 +20,7 @@ def initiate(token_addr: address, token_quantity: currency_value): self.invariant = msg.value * token_quantity assert self.invariant > 0 +# Sells ether to the contract in exchange for tokens (minus a fee) @public @payable def eth_to_tokens(): @@ -28,6 +33,7 @@ def eth_to_tokens(): self.total_eth_qty = new_total_eth self.total_token_qty = new_total_tokens +# Sells tokens to the contract in exchange for ether @public def tokens_to_eth(sell_quantity: currency_value): self.token_address.transferFrom(msg.sender, self, as_num256(sell_quantity)) @@ -38,6 +44,7 @@ def tokens_to_eth(sell_quantity: currency_value): self.total_eth_qty = new_total_eth self.total_token_qty = new_total_tokens +# Owner can withdraw their funds and destroy the market maker @public def owner_withdraw(): assert self.owner == msg.sender From 8efc8e0012d63fb8d4645f89a19ea92243572e49 Mon Sep 17 00:00:00 2001 From: David Knott Date: Mon, 25 Dec 2017 15:58:18 +0700 Subject: [PATCH 162/162] Change badges from viper to vyper --- README.md | 14 +++++++------- docs/index.rst | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b3677a1591..bd724b1f9d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -[![Join the chat at https://gitter.im/bethereum/viper](https://badges.gitter.im/ethereum/viper.svg)](https://gitter.im/ethereum/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://travis-ci.org/ethereum/viper.svg?branch=master)](https://travis-ci.org/ethereum/viper) -[![Documentation Status](https://readthedocs.org/projects/viper/badge/?version=latest)](http://viper.readthedocs.io/en/latest/?badge=latest) -[![Coverage Status](https://coveralls.io/repos/github/ethereum/viper/badge.svg?branch=master)](https://coveralls.io/github/ethereum/viper?branch=master) +[![Join the chat at https://gitter.im/bethereum/vyper](https://badges.gitter.im/ethereum/vyper.svg)](https://gitter.im/ethereum/vyper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Build Status](https://travis-ci.org/ethereum/vyper.svg?branch=master)](https://travis-ci.org/ethereum/vyper) +[![Documentation Status](https://readthedocs.org/projects/vyper/badge/?version=latest)](http://vyper.readthedocs.io/en/latest/?badge=latest) +[![Coverage Status](https://coveralls.io/repos/github/ethereum/vyper/badge.svg?branch=master)](https://coveralls.io/github/ethereum/vyper?branch=master) # Principles and Goals @@ -31,7 +31,7 @@ Viper does NOT strive to be a 100% replacement for everything that can be done i **Note: Viper is still alpha software, use with care** # Installation -See the [Viper documentation](https://viper.readthedocs.io/en/latest/installing-viper.html) +See the [Viper documentation](https://vyper.readthedocs.io/en/latest/installing-viper.html) for build instructions. # Compiling a contract @@ -59,5 +59,5 @@ For testing strategy, please see [Testing](no-link) # Contributing * See Issues tab, and feel free to submit your own issues * Add PRs if you discover a solution to an existing issue -* For further discussions and questions talk to us on [gitter](https://gitter.im/ethereum/viper) -* For more information, see [Contributing](http://viper.readthedocs.io/en/latest/contributing.html) +* For further discussions and questions talk to us on [gitter](https://gitter.im/ethereum/vyper) +* For more information, see [Contributing](http://vyper.readthedocs.io/en/latest/contributing.html) diff --git a/docs/index.rst b/docs/index.rst index f308905871..175426a0b3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,6 +48,7 @@ Following the principles and goals, Viper **does not** provide the following fea Compatibility-breaking Changelog ******************************** +* **2017.12.25**: Change name from Viper to Vyper * **2017.11.29**: ``@internal`` renamed to ``@private``. * **2017.11.15**: Functions require either ``@internal`` or ``@public`` decorators. * **2017.07.25**: The ``def foo() -> num(const): ...`` syntax no longer works; you now need to do ``def foo() -> num: ...`` with a ``@constant`` decorator on the previous line.