Skip to content

Commit

Permalink
Merge master.
Browse files Browse the repository at this point in the history
  • Loading branch information
jacqueswww committed May 16, 2018
1 parent f78a259 commit 72e36c0
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 27 deletions.
3 changes: 2 additions & 1 deletion docs/testing-deploying-contracts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ One option is to take the bytecode generated by the vyper compiler and manually
vyper yourFileName.vy
# returns bytecode
Deploying from bytecode is currently outside the scope of this example.
Or deploy it with current browser on `myetherwallet <https://www.myetherwallet.com/#contracts>`_ contract menu.

We are working on integration with `populus <https://github.com/ethereum/populus/issues/372>`_,
this will be the preferred way of deploying vyper contracts in the future.
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@


setattr(eth_tester.backends.pyevm.main, 'GENESIS_GAS_LIMIT', 10**9)
setattr(eth_tester.backends.pyevm.main, 'GENESIS_DIFFICULTY', 1)


@pytest.fixture(autouse=True)
Expand Down
5 changes: 0 additions & 5 deletions tests/examples/voting/test_ballot.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import pytest


def setUp(self):
# Initialize tester, contract and expose relevant objects
self.s.head_state.gas_limit = 10**7


@pytest.fixture
def c(get_contract):
with open('examples/voting/ballot.v.py') as f:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
from vyper.exceptions import StructureException, VariableDeclarationException, InvalidTypeException
from vyper.exceptions import (
StructureException,
VariableDeclarationException,
InvalidTypeException,
TypeMismatchException
)


def test_external_contract_calls(get_contract, get_contract_with_gas_estimation):
Expand Down Expand Up @@ -540,3 +545,110 @@ def foo(contract_address: contract(Bar)) -> int128:
"""

assert_compile_failed(lambda: get_contract(contract_1), InvalidTypeException)


def test_external_with_payble_value(w3, get_contract_with_gas_estimation):
contract_1 = """
@payable
@public
def get_lucky() -> int128:
return 1
@public
def get_balance() -> int128(wei):
return self.balance
"""

contract_2 = """
class Bar():
def set_lucky(arg1: int128): pass
def get_lucky() -> int128: pass
bar_contract: modifiable(Bar)
@public
def set_contract(contract_address: contract(Bar)):
self.bar_contract = contract_address
@payable
@public
def get_lucky(amount_to_send: int128) -> int128:
if amount_to_send != 0:
return self.bar_contract.get_lucky(value=amount_to_send)
else: # send it all
return self.bar_contract.get_lucky(value=msg.value)
"""

c1 = get_contract_with_gas_estimation(contract_1)
c2 = get_contract_with_gas_estimation(contract_2)

# Set address.
assert c1.get_lucky() == 1
assert c1.get_balance() == 0

c2.set_contract(c1.address, transact={})

# Send some eth
assert c2.get_lucky(0, call={'value': 500}) == 1
c2.get_lucky(0, transact={'value': 500})
# Contract 1 received money.
assert c1.get_balance() == 500
assert w3.eth.getBalance(c1.address) == 500
assert w3.eth.getBalance(c2.address) == 0

# Send subset of amount
assert c2.get_lucky(250, call={'value': 500}) == 1
c2.get_lucky(250, transact={'value': 500})

# Contract 1 received more money.
assert c1.get_balance() == 750
assert w3.eth.getBalance(c1.address) == 750
assert w3.eth.getBalance(c2.address) == 250


def test_external_call_with_gas(assert_tx_failed, get_contract_with_gas_estimation):
contract_1 = """
@public
def get_lucky() -> int128:
return 656598
"""

contract_2 = """
class Bar():
def set_lucky(arg1: int128): pass
def get_lucky() -> int128: pass
bar_contract: modifiable(Bar)
@public
def set_contract(contract_address: contract(Bar)):
self.bar_contract = contract_address
@public
def get_lucky(gas_amount: int128) -> int128:
return self.bar_contract.get_lucky(gas=gas_amount)
"""

c1 = get_contract_with_gas_estimation(contract_1)
c2 = get_contract_with_gas_estimation(contract_2)
c2.set_contract(c1.address, transact={})

assert c2.get_lucky(1000) == 656598
assert_tx_failed(lambda: c2.get_lucky(100)) # too little gas.


def test_invalid_keyword_on_call(assert_compile_failed, get_contract_with_gas_estimation):

contract_1 = """
class Bar():
def set_lucky(arg1: int128): pass
def get_lucky() -> int128: pass
bar_contract: modifiable(Bar)
@public
def get_lucky(amount_to_send: int128) -> int128:
return self.bar_contract.get_lucky(gass=1)
"""

assert_compile_failed(lambda: get_contract_with_gas_estimation(contract_1), TypeMismatchException)
1 change: 1 addition & 0 deletions tests/parser/features/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ def ioo(inp: bytes[100]):
"""

c = get_contract_with_gas_estimation(loggy_code)

tx_hash = c.foo(transact={})
receipt = tester.get_transaction_receipt(tx_hash.hex())
logs = receipt['logs']
Expand Down
63 changes: 63 additions & 0 deletions tests/parser/types/test_bytes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from vyper.exceptions import (
ParserException,
TypeMismatchException
)


def test_test_bytes(get_contract_with_gas_estimation, assert_tx_failed):
Expand Down Expand Up @@ -186,3 +190,62 @@ def bar_storage() -> int128:
assert_tx_failed(lambda: c.foo(b"\x01" * 33))
assert c.bar_storage() == 97
print('Passed bytes_to_num tests')


def test_binary_literal(get_contract_with_gas_estimation):
bytes_to_num_code = """
r: bytes[1]
@public
def get(a: bytes[1]) -> bytes[2]:
return concat(a, 0b0001)
@public
def getsome() -> bytes[1]:
return 0b1110
@public
def testsome(a: bytes[1]) -> bool:
return a == 0b1100001
@public
def testsome_storage(y: bytes[1]) -> bool:
self.r = 0b1100001
return self.r == y
"""

c = get_contract_with_gas_estimation(bytes_to_num_code)

assert c.getsome() == b'\x0e'
assert c.testsome(b'a')
assert c.testsome(b'\x61')
assert c.testsome(0b1100001.to_bytes(1, 'big'))
assert not c.testsome(b'b')
assert c.testsome_storage(b'a')
assert not c.testsome_storage(b'x')


def test_bytes_comparison_fail_size_mismatch(assert_compile_failed, get_contract_with_gas_estimation):
code = """
@public
def get(a: bytes[1]) -> bool:
b: bytes[2] = 'ab'
return a == b
"""
assert_compile_failed(
lambda: get_contract_with_gas_estimation(code),
TypeMismatchException
)


def test_bytes_comparison_fail_too_large(assert_compile_failed, get_contract_with_gas_estimation):
code = """
@public
def get(a: bytes[100]) -> bool:
b: bytes[100] = 'ab'
return a == b
"""
assert_compile_failed(
lambda: get_contract_with_gas_estimation(code),
ParserException
)
6 changes: 3 additions & 3 deletions vyper/functions/signature.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ast

from vyper.parser.parser_utils import (
get_original_if_0x_prefixed,
get_original_if_0_prefixed,
)
from vyper.exceptions import (
TypeMismatchException,
Expand Down Expand Up @@ -35,10 +35,10 @@ def process_arg(index, arg, expected_arg_typelist, function_name, context):
vsub = None
for expected_arg in expected_arg_typelist:
if expected_arg == 'num_literal':
if isinstance(arg, ast.Num) and get_original_if_0x_prefixed(arg, context) is None:
if isinstance(arg, ast.Num) and get_original_if_0_prefixed(arg, context) is None:
return arg.n
elif expected_arg == 'str_literal':
if isinstance(arg, ast.Str) and get_original_if_0x_prefixed(arg, context) is None:
if isinstance(arg, ast.Str) and get_original_if_0_prefixed(arg, context) is None:
bytez = b''
for c in arg.s:
if ord(c) >= 256:
Expand Down
70 changes: 63 additions & 7 deletions vyper/parser/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
StructureException,
TypeMismatchException,
VariableDeclarationException,
ParserException,
ParserException
)
from .parser_utils import LLLnode
from .parser_utils import (
getpos,
unwrap_location,
get_original_if_0x_prefixed,
get_original_if_0_prefixed,
get_number_as_fraction,
add_variable_offset,
)
Expand Down Expand Up @@ -78,7 +78,7 @@ def get_expr(self):
return self.expr

def number(self):
orignum = get_original_if_0x_prefixed(self.expr, self.context)
orignum = get_original_if_0_prefixed(self.expr, self.context)

if orignum is None and isinstance(self.expr.n, int):

Expand All @@ -98,6 +98,25 @@ def number(self):
if DECIMAL_DIVISOR % den:
raise InvalidLiteralException("Too many decimal places: " + numstring, self.expr)
return LLLnode.from_list(num * DECIMAL_DIVISOR // den, typ=BaseType('decimal', None), pos=getpos(self.expr))
# Binary literal.
elif orignum[:2] == '0b':
str_val = orignum[2:]
total_bits = len(orignum[2:])
total_bits = total_bits if total_bits % 8 == 0 else total_bits + 8 - (total_bits % 8) # ceil8 to get byte length.
if total_bits != len(orignum[2:]): # add necessary zero padding.
pad_len = total_bits - len(orignum[2:])
str_val = pad_len * '0' + str_val
byte_len = int(total_bits / 8)
placeholder = self.context.new_placeholder(ByteArrayType(byte_len))
seq = []
seq.append(['mstore', placeholder, byte_len])
for i in range(0, total_bits, 256):
section = str_val[i:i + 256]
int_val = int(section, 2) << (256 - len(section)) # bytes are right padded.
seq.append(
['mstore', ['add', placeholder, i + 32], int_val])
return LLLnode.from_list(['seq'] + seq + [placeholder],
typ=ByteArrayType(byte_len), location='memory', pos=getpos(self.expr), annotation='Create ByteArray (Binary literal): %s' % str_val)
elif len(orignum) == 42:
if checksum_encode(orignum) != orignum:
raise InvalidLiteralException("Address checksum mismatch. If you are sure this is the "
Expand Down Expand Up @@ -452,14 +471,21 @@ def build_in_comparator(self):
def compare(self):
left = Expr.parse_value_expr(self.expr.left, self.context)
right = Expr.parse_value_expr(self.expr.comparators[0], self.context)
if isinstance(self.expr.ops[0], ast.In) and \

if isinstance(left.typ, ByteArrayType) and isinstance(right.typ, ByteArrayType):
if left.typ.maxlen != right.typ.maxlen:
raise TypeMismatchException('Can only compare bytes of the same length', self.expr)
if left.typ.maxlen > 32 or right.typ.maxlen > 32:
raise ParserException('Can only compare bytes of length shorter than 32 bytes', self.expr)
elif isinstance(self.expr.ops[0], ast.In) and \
isinstance(right.typ, ListType):
if not are_units_compatible(left.typ, right.typ.subtype) and not are_units_compatible(right.typ.subtype, left.typ):
raise TypeMismatchException("Can't use IN comparison with different types!", self.expr)
return self.build_in_comparator()
else:
if not are_units_compatible(left.typ, right.typ) and not are_units_compatible(right.typ, left.typ):
raise TypeMismatchException("Can't compare values with different units!", self.expr)

if len(self.expr.ops) != 1:
raise StructureException("Cannot have a comparison with more than two elements", self.expr)
if isinstance(self.expr.ops[0], ast.Gt):
Expand All @@ -476,6 +502,22 @@ def compare(self):
op = 'ne'
else:
raise Exception("Unsupported comparison operator")

# Compare (limited to 32) byte arrays.
if isinstance(left.typ, ByteArrayType) and isinstance(left.typ, ByteArrayType):
left = Expr(self.expr.left, self.context).lll_node
right = Expr(self.expr.comparators[0], self.context).lll_node

def load_bytearray(side):
if side.location == 'memory':
return ['mload', ['add', 32, side]]
elif side.location == 'storage':
return ['sload', ['add', 1, ['sha3_32', side]]]

return LLLnode.from_list(
[op, load_bytearray(left), load_bytearray(right)], typ='bool', pos=getpos(self.expr))

# Compare other types.
if not is_numeric_type(left.typ) or not is_numeric_type(right.typ):
if op not in ('eq', 'ne'):
raise TypeMismatchException("Invalid type for comparison op", self.expr)
Expand Down Expand Up @@ -522,6 +564,17 @@ def unary_operations(self):
else:
raise StructureException("Only the 'not' unary operator is supported")

def _get_external_contract_keywords(self):
value, gas = None, None
for kw in self.expr.keywords:
if kw.arg not in ('value', 'gas'):
raise TypeMismatchException('Invalid keyword argument, only "gas" and "value" supported.', self.expr)
elif kw.arg == 'gas':
gas = Expr.parse_value_expr(kw.value, self.context)
elif kw.arg == 'value':
value = Expr.parse_value_expr(kw.value, self.context)
return value, gas

# Function calls
def call(self):
from .parser import (
Expand Down Expand Up @@ -576,17 +629,20 @@ def call(self):
elif isinstance(self.expr.func, ast.Attribute) and isinstance(self.expr.func.value, ast.Call):
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(self.expr, self.context, contract_name, contract_address, True, pos=getpos(self.expr))
value, gas = self._get_external_contract_keywords()
return external_contract_call(self.expr, self.context, contract_name, contract_address, True, pos=getpos(self.expr), value=value, gas=gas)
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
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(self.expr, self.context, contract_name, contract_address, True, pos=getpos(self.expr))
value, gas = self._get_external_contract_keywords()
return external_contract_call(self.expr, self.context, contract_name, contract_address, True, pos=getpos(self.expr), value=value, gas=gas)
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(self.expr, self.context, contract_name, contract_address, var.modifiable, pos=getpos(self.expr))
value, gas = self._get_external_contract_keywords()
return external_contract_call(self.expr, self.context, contract_name, contract_address, var.modifiable, pos=getpos(self.expr), value=value, gas=gas)
else:
raise StructureException("Unsupported operator: %r" % ast.dump(self.expr), self.expr)

Expand Down
Loading

0 comments on commit 72e36c0

Please sign in to comment.