From b43fface148b45cbd2c90b5d24f77398a63745b5 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 9 Apr 2024 20:11:51 -0400 Subject: [PATCH] feat[lang]!: change ABI type of `decimal` to `int168` (#3696) this commit changes how decimals are exposed in the ABI - changing the ABI type from `fixed168x10` to `int168`. this is done for compatibility reaosns, since `fixedMxN` types are not widely supported. it is also a preparatory breaking change for supporting more decimal types, in the future, we might expand the set of decimal types, e.g. something along the lines of `Decimal[bits, places]`. in such a future, if we add `UDecimal[256, 18]` (colloquially known as `"wad"`), it would be useful to users if it had an ABI type of uint256, for compatibility with common ERCs. to distinguish from plain `int168` in the JSON ABI, an `"internalType"` field is added, which includes the decimal info. in the future, if we add more decimal types, it would be distinguished from other decimal types according to the metadata, ex.: `{"internalType": {"decimal": {"bits": 168, "places": 10}}}`. misc/refactor: - remove `FixedMxN` abi types - add a `decimal_to_int()` utility function to make test migration cleaner. it essentially emulates what the compiler does internally during codegen, which is that it takes the arguments to a `Decimal` and then bitcasts the resulting `Decimal` to an int. --------- Co-authored-by: tserg <8017125+tserg@users.noreply.github.com> --- docs/types.rst | 2 +- .../builtins/codegen/test_abi_decode.py | 21 +++--- .../builtins/codegen/test_abi_encode.py | 7 +- .../builtins/codegen/test_as_wei_value.py | 15 ++-- .../functional/builtins/codegen/test_ceil.py | 6 +- .../builtins/codegen/test_convert.py | 29 ++++++-- .../functional/builtins/codegen/test_floor.py | 6 +- .../builtins/codegen/test_minmax.py | 5 +- .../builtins/codegen/test_minmax_value.py | 2 +- .../functional/builtins/codegen/test_unary.py | 7 +- .../builtins/folding/test_epsilon.py | 4 +- .../builtins/folding/test_floor_ceil.py | 5 +- .../folding/test_fold_as_wei_value.py | 5 +- .../builtins/folding/test_min_max.py | 5 +- .../test_external_contract_calls.py | 7 +- .../test_self_call_struct.py | 14 ++-- .../codegen/features/iteration/test_break.py | 21 +++--- .../features/iteration/test_for_in_list.py | 14 ++-- .../codegen/features/test_clampers.py | 5 +- .../codegen/features/test_internal_call.py | 10 +-- .../codegen/features/test_logging.py | 27 ++++---- tests/functional/codegen/test_interfaces.py | 6 +- .../codegen/types/numbers/test_constants.py | 22 +++--- .../codegen/types/numbers/test_decimals.py | 69 +++++++++++-------- .../codegen/types/numbers/test_modulo.py | 11 ++- .../codegen/types/numbers/test_sqrt.py | 32 +++++---- .../codegen/types/test_dynamic_array.py | 20 ++++-- tests/functional/codegen/types/test_lists.py | 29 ++++++-- .../unit/abi_types/test_invalid_abi_types.py | 10 +-- .../unit/ast/nodes/test_fold_binop_decimal.py | 9 ++- tests/unit/compiler/test_abi.py | 33 +++++++++ tests/unit/semantics/types/test_event.py | 13 ++-- tests/utils.py | 7 ++ vyper/abi_types.py | 29 -------- vyper/semantics/types/base.py | 2 +- vyper/semantics/types/primitives.py | 11 ++- vyper/semantics/types/utils.py | 8 +-- 37 files changed, 303 insertions(+), 225 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 86b5cdde49..f82153b1b9 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -239,7 +239,7 @@ A value with a precision of 10 decimal places between -1870722095783555735300716 In order for a literal to be interpreted as ``decimal`` it must include a decimal point. -The ABI type (for computing method identifiers) of ``decimal`` is ``fixed168x10``. +The ABI type (for computing method identifiers) of ``decimal`` is ``int168``. Operators ********* diff --git a/tests/functional/builtins/codegen/test_abi_decode.py b/tests/functional/builtins/codegen/test_abi_decode.py index 213738957b..67a1f56e87 100644 --- a/tests/functional/builtins/codegen/test_abi_decode.py +++ b/tests/functional/builtins/codegen/test_abi_decode.py @@ -1,8 +1,7 @@ -from decimal import Decimal - import pytest from eth.codecs import abi +from tests.utils import decimal_to_int from vyper.exceptions import ArgumentException, StackTooDeep, StructureException TEST_ADDR = "0x" + b"".join(chr(i).encode("utf-8") for i in range(20)).hex() @@ -54,22 +53,28 @@ def abi_decode_struct(x: Bytes[544]) -> Human: c = get_contract(contract) test_bytes32 = b"".join(chr(i).encode("utf-8") for i in range(32)) - args = (TEST_ADDR, -1, True, Decimal("-123.4"), test_bytes32) - encoding = "(address,int128,bool,fixed168x10,bytes32)" + args = (TEST_ADDR, -1, True, decimal_to_int("-123.4"), test_bytes32) + encoding = "(address,int128,bool,int168,bytes32)" encoded = abi.encode(encoding, args) - assert tuple(c.abi_decode(encoded)) == (TEST_ADDR, -1, True, Decimal("-123.4"), test_bytes32) + assert tuple(c.abi_decode(encoded)) == ( + TEST_ADDR, + -1, + True, + decimal_to_int("-123.4"), + test_bytes32, + ) test_bytes32 = b"".join(chr(i).encode("utf-8") for i in range(32)) human_tuple = ( "foobar", - ("vyper", TEST_ADDR, 123, True, Decimal("123.4"), [123, 456, 789], test_bytes32), + ("vyper", TEST_ADDR, 123, True, decimal_to_int("123.4"), [123, 456, 789], test_bytes32), ) args = tuple([human_tuple[0]] + list(human_tuple[1])) - human_t = "((string,(string,address,int128,bool,fixed168x10,uint256[3],bytes32)))" + human_t = "((string,(string,address,int128,bool,int168,uint256[3],bytes32)))" human_encoded = abi.encode(human_t, (human_tuple,)) assert tuple(c.abi_decode_struct(human_encoded)) == ( "foobar", - ("vyper", TEST_ADDR, 123, True, Decimal("123.4"), [123, 456, 789], test_bytes32), + ("vyper", TEST_ADDR, 123, True, decimal_to_int("123.4"), [123, 456, 789], test_bytes32), ) diff --git a/tests/functional/builtins/codegen/test_abi_encode.py b/tests/functional/builtins/codegen/test_abi_encode.py index 305c4b1356..2331fb1a9e 100644 --- a/tests/functional/builtins/codegen/test_abi_encode.py +++ b/tests/functional/builtins/codegen/test_abi_encode.py @@ -1,8 +1,7 @@ -from decimal import Decimal - import pytest from eth.codecs import abi +from tests.utils import decimal_to_int from vyper.exceptions import StackTooDeep @@ -109,10 +108,10 @@ def abi_encode3(x: uint256, ensure_tuple: bool, include_method_id: bool) -> Byte test_bytes32 = b"".join(chr(i).encode("utf-8") for i in range(32)) human_tuple = ( "foobar", - ("vyper", test_addr, 123, True, Decimal("123.4"), [123, 456, 789], test_bytes32), + ("vyper", test_addr, 123, True, decimal_to_int("123.4"), [123, 456, 789], test_bytes32), ) args = tuple([human_tuple[0]] + list(human_tuple[1])) - human_t = "(string,(string,address,int128,bool,fixed168x10,uint256[3],bytes32))" + human_t = "(string,(string,address,int128,bool,int168,uint256[3],bytes32))" human_encoded = abi.encode(human_t, human_tuple) assert c.abi_encode(*args, False, False).hex() == human_encoded.hex() assert c.abi_encode(*args, False, True).hex() == (method_id + human_encoded).hex() diff --git a/tests/functional/builtins/codegen/test_as_wei_value.py b/tests/functional/builtins/codegen/test_as_wei_value.py index d0a2a948be..7ceaefd347 100644 --- a/tests/functional/builtins/codegen/test_as_wei_value.py +++ b/tests/functional/builtins/codegen/test_as_wei_value.py @@ -1,7 +1,9 @@ -from decimal import Decimal - import pytest +from tests.utils import decimal_to_int +from vyper.semantics.types import DecimalT +from vyper.utils import quantize, round_towards_zero + wei_denoms = { "femtoether": 3, "kwei": 3, @@ -61,11 +63,14 @@ def test_wei_decimal(get_contract, tx_failed, denom, multiplier): def foo(a: decimal) -> uint256: return as_wei_value(a, "{denom}") """ - c = get_contract(code) - value = Decimal((2**127 - 1) / (10**multiplier)) - assert c.foo(value) == value * (10**multiplier) + denom_int = 10**multiplier + # TODO: test with more values + _, hi = DecimalT().ast_bounds + value = quantize(hi / denom_int) + + assert c.foo(decimal_to_int(value)) == round_towards_zero(value * denom_int) @pytest.mark.parametrize("value", (-1, -(2**127))) diff --git a/tests/functional/builtins/codegen/test_ceil.py b/tests/functional/builtins/codegen/test_ceil.py index a5b5cbc561..be8efd3e70 100644 --- a/tests/functional/builtins/codegen/test_ceil.py +++ b/tests/functional/builtins/codegen/test_ceil.py @@ -1,6 +1,8 @@ import math from decimal import Decimal +from tests.utils import decimal_to_int + def test_ceil(get_contract_with_gas_estimation): code = """ @@ -102,8 +104,8 @@ def ceil_param(p: decimal) -> int256: assert c.fos() == -5472 assert c.fot() == math.ceil(-(Decimal(2**167 - 1)) / 10**10) assert c.fou() == -3 - assert c.ceil_param(Decimal("-0.5")) == 0 - assert c.ceil_param(Decimal("-7777777.7777777")) == -7777777 + assert c.ceil_param(decimal_to_int("-0.5")) == 0 + assert c.ceil_param(decimal_to_int("-7777777.7777777")) == -7777777 def test_ceil_ext_call(w3, side_effects_contract, assert_side_effects_invoked, get_contract): diff --git a/tests/functional/builtins/codegen/test_convert.py b/tests/functional/builtins/codegen/test_convert.py index 27a3d0d519..423bee744d 100644 --- a/tests/functional/builtins/codegen/test_convert.py +++ b/tests/functional/builtins/codegen/test_convert.py @@ -1,13 +1,13 @@ import enum import itertools - -# import random +import math from decimal import Decimal import eth.codecs.abi as abi import eth.codecs.abi.exceptions import pytest +from tests.utils import decimal_to_int from vyper.compiler import compile_code from vyper.exceptions import InvalidLiteral, InvalidType, TypeMismatch from vyper.semantics.types import AddressT, BoolT, BytesM_T, BytesT, DecimalT, IntegerT, StringT @@ -249,13 +249,20 @@ def _padconvert(val_bits, direction, n, padding_byte=None): def _from_bits(val_bits, o_typ): # o_typ: the type to convert to try: - return abi.decode(o_typ.abi_type.selector_name(), val_bits) + ret = abi.decode(o_typ.abi_type.selector_name(), val_bits) + if isinstance(o_typ, DecimalT): + return Decimal(ret) / o_typ.divisor + return ret except eth.codecs.abi.exceptions.DecodeError: raise _OutOfBounds() from None def _to_bits(val, i_typ): # i_typ: the type to convert from + if isinstance(i_typ, DecimalT): + val = val * i_typ.divisor + assert math.ceil(val) == math.floor(val) + val = int(val) return abi.encode(i_typ.abi_type.selector_name(), val) @@ -430,6 +437,13 @@ def test_convert_passing( # web3 has special formatter for zero address expected_val = None + if isinstance(o_typ, DecimalT): + expected_val = decimal_to_int(expected_val) + + input_val = val + if isinstance(i_typ, DecimalT): + input_val = decimal_to_int(val) + contract_1 = f""" @external def test_convert() -> {o_typ}: @@ -461,7 +475,7 @@ def test_input_convert(x: {i_typ}) -> {o_typ}: """ c2 = get_contract_with_gas_estimation(contract_2) - assert c2.test_input_convert(val) == expected_val + assert c2.test_input_convert(input_val) == expected_val contract_3 = f""" bar: {i_typ} @@ -483,7 +497,7 @@ def test_memory_variable_convert(x: {i_typ}) -> {o_typ}: """ c4 = get_contract_with_gas_estimation(contract_4) - assert c4.test_memory_variable_convert(val) == expected_val + assert c4.test_memory_variable_convert(input_val) == expected_val @pytest.mark.parametrize("typ", ["uint8", "int128", "int256", "uint256"]) @@ -715,5 +729,8 @@ def foo(bar: {i_typ}) -> {o_typ}: """ c3 = get_contract_with_gas_estimation(contract_3) + input_val = val + if isinstance(i_typ, DecimalT): + input_val = decimal_to_int(input_val) with tx_failed(): - c3.foo(val) + c3.foo(input_val) diff --git a/tests/functional/builtins/codegen/test_floor.py b/tests/functional/builtins/codegen/test_floor.py index 968cf1ec91..1063579531 100644 --- a/tests/functional/builtins/codegen/test_floor.py +++ b/tests/functional/builtins/codegen/test_floor.py @@ -1,6 +1,8 @@ import math from decimal import Decimal +from tests.utils import decimal_to_int + def test_floor(get_contract_with_gas_estimation): code = """ @@ -106,8 +108,8 @@ def floor_param(p: decimal) -> int256: assert c.fos() == -1 assert c.fot() == math.floor(-Decimal(2**167) / 10**10) assert c.fou() == -4 - assert c.floor_param(Decimal("-5.6")) == -6 - assert c.floor_param(Decimal("-0.0000000001")) == -1 + assert c.floor_param(decimal_to_int("-5.6")) == -6 + assert c.floor_param(decimal_to_int("-0.0000000001")) == -1 def test_floor_ext_call(w3, side_effects_contract, assert_side_effects_invoked, get_contract): diff --git a/tests/functional/builtins/codegen/test_minmax.py b/tests/functional/builtins/codegen/test_minmax.py index f86504522f..e339547b37 100644 --- a/tests/functional/builtins/codegen/test_minmax.py +++ b/tests/functional/builtins/codegen/test_minmax.py @@ -1,7 +1,6 @@ -from decimal import Decimal - import pytest +from tests.utils import decimal_to_int from vyper.semantics.types import IntegerT @@ -17,7 +16,7 @@ def goo() -> uint256: """ c = get_contract_with_gas_estimation(minmax_test) - assert c.foo() == Decimal("58223.123") + assert c.foo() == decimal_to_int("58223.123") assert c.goo() == 83 print("Passed min/max test") diff --git a/tests/functional/builtins/codegen/test_minmax_value.py b/tests/functional/builtins/codegen/test_minmax_value.py index c5ee5c3584..0b0ede51ae 100644 --- a/tests/functional/builtins/codegen/test_minmax_value.py +++ b/tests/functional/builtins/codegen/test_minmax_value.py @@ -15,7 +15,7 @@ def foo() -> {typ}: """ c = get_contract(code) - lo, hi = typ.ast_bounds + lo, hi = typ.int_bounds if op == "min_value": assert c.foo() == lo elif op == "max_value": diff --git a/tests/functional/builtins/codegen/test_unary.py b/tests/functional/builtins/codegen/test_unary.py index 2be5c0d33f..6ac6675755 100644 --- a/tests/functional/builtins/codegen/test_unary.py +++ b/tests/functional/builtins/codegen/test_unary.py @@ -1,7 +1,6 @@ -from decimal import Decimal - import pytest +from tests.utils import decimal_to_int from vyper.exceptions import InvalidOperation @@ -63,8 +62,8 @@ def bar() -> decimal: """ c = get_contract(code) - assert c.foo() == Decimal("-18707220957835557353007165858768422651595.9365500927") - assert c.bar() == Decimal("18707220957835557353007165858768422651595.9365500927") + assert c.foo() == decimal_to_int("-18707220957835557353007165858768422651595.9365500927") + assert c.bar() == decimal_to_int("18707220957835557353007165858768422651595.9365500927") def test_negation_int128(get_contract): diff --git a/tests/functional/builtins/folding/test_epsilon.py b/tests/functional/builtins/folding/test_epsilon.py index 7bc2afe757..ddec237126 100644 --- a/tests/functional/builtins/folding/test_epsilon.py +++ b/tests/functional/builtins/folding/test_epsilon.py @@ -1,6 +1,6 @@ import pytest -from tests.utils import parse_and_fold +from tests.utils import decimal_to_int, parse_and_fold @pytest.mark.parametrize("typ_name", ["decimal"]) @@ -16,4 +16,4 @@ def foo() -> {typ_name}: old_node = vyper_ast.body[0].value new_node = old_node.get_folded_value() - assert contract.foo() == new_node.value + assert contract.foo() == decimal_to_int(new_node.value) diff --git a/tests/functional/builtins/folding/test_floor_ceil.py b/tests/functional/builtins/folding/test_floor_ceil.py index 9e63c7b099..08408d1783 100644 --- a/tests/functional/builtins/folding/test_floor_ceil.py +++ b/tests/functional/builtins/folding/test_floor_ceil.py @@ -4,7 +4,7 @@ from hypothesis import example, given, settings from hypothesis import strategies as st -from tests.utils import parse_and_fold +from tests.utils import decimal_to_int, parse_and_fold st_decimals = st.decimals( min_value=-(2**32), max_value=2**32, allow_nan=False, allow_infinity=False, places=10 @@ -31,4 +31,5 @@ def foo(a: decimal) -> int256: old_node = vyper_ast.body[0].value new_node = old_node.get_folded_value() - assert contract.foo(value) == new_node.value + assert isinstance(new_node.value, int) + assert contract.foo(decimal_to_int(value)) == new_node.value diff --git a/tests/functional/builtins/folding/test_fold_as_wei_value.py b/tests/functional/builtins/folding/test_fold_as_wei_value.py index 01af646a16..55961a958d 100644 --- a/tests/functional/builtins/folding/test_fold_as_wei_value.py +++ b/tests/functional/builtins/folding/test_fold_as_wei_value.py @@ -2,7 +2,7 @@ from hypothesis import given, settings from hypothesis import strategies as st -from tests.utils import parse_and_fold +from tests.utils import decimal_to_int, parse_and_fold from vyper.builtins import functions as vy_fn from vyper.utils import SizeLimits @@ -34,7 +34,8 @@ def foo(a: decimal) -> uint256: old_node = vyper_ast.body[0].value new_node = old_node.get_folded_value() - assert contract.foo(value) == new_node.value + assert isinstance(new_node.value, int) + assert contract.foo(decimal_to_int(value)) == new_node.value @pytest.mark.fuzzing diff --git a/tests/functional/builtins/folding/test_min_max.py b/tests/functional/builtins/folding/test_min_max.py index 752b64eb04..22795e93c7 100644 --- a/tests/functional/builtins/folding/test_min_max.py +++ b/tests/functional/builtins/folding/test_min_max.py @@ -2,7 +2,7 @@ from hypothesis import given, settings from hypothesis import strategies as st -from tests.utils import parse_and_fold +from tests.utils import decimal_to_int, parse_and_fold from vyper.utils import SizeLimits st_decimals = st.decimals( @@ -32,7 +32,8 @@ def foo(a: decimal, b: decimal) -> decimal: old_node = vyper_ast.body[0].value new_node = old_node.get_folded_value() - assert contract.foo(left, right) == new_node.value + l, r = [decimal_to_int(t) for t in (left, right)] + assert contract.foo(l, r) == decimal_to_int(new_node.value) @pytest.mark.fuzzing diff --git a/tests/functional/codegen/calling_convention/test_external_contract_calls.py b/tests/functional/codegen/calling_convention/test_external_contract_calls.py index 6cec121f3b..2125b6c4a8 100644 --- a/tests/functional/codegen/calling_convention/test_external_contract_calls.py +++ b/tests/functional/codegen/calling_convention/test_external_contract_calls.py @@ -1,8 +1,7 @@ -from decimal import Decimal - import pytest from eth.codecs import abi +from tests.utils import decimal_to_int from vyper import compile_code from vyper.exceptions import ( ArgumentException, @@ -517,7 +516,7 @@ def bar(arg1: address) -> decimal: """ c2 = get_contract(contract_2) - assert c2.bar(c.address) == Decimal("1e-10") + assert c2.bar(c.address) == decimal_to_int("1e-10") def test_decimal_too_long(get_contract, tx_failed): @@ -570,7 +569,7 @@ def bar(arg1: address) -> (decimal, Bytes[3], decimal): c2 = get_contract(contract_2) assert c.foo() == [0, b"dog", 1] result = c2.bar(c.address) - assert result == [Decimal("0.0"), b"dog", Decimal("1e-10")] + assert result == [decimal_to_int("0.0"), b"dog", decimal_to_int("1e-10")] @pytest.mark.parametrize("a,b", [(8, 256), (256, 8), (256, 256)]) diff --git a/tests/functional/codegen/calling_convention/test_self_call_struct.py b/tests/functional/codegen/calling_convention/test_self_call_struct.py index f3ec96f1c0..56b36c6492 100644 --- a/tests/functional/codegen/calling_convention/test_self_call_struct.py +++ b/tests/functional/codegen/calling_convention/test_self_call_struct.py @@ -1,4 +1,4 @@ -from decimal import Decimal +from tests.utils import decimal_to_int def test_call_to_self_struct(w3, get_contract): @@ -24,12 +24,12 @@ def wrap_get_my_struct_BROKEN(_e1: decimal) -> MyStruct: return self.get_my_struct(_e1, block.timestamp) """ c = get_contract(code) - assert c.wrap_get_my_struct_WORKING(Decimal("0.1")) == ( - Decimal("0.1"), + assert c.wrap_get_my_struct_WORKING(decimal_to_int("0.1")) == ( + decimal_to_int("0.1"), w3.eth.get_block(w3.eth.block_number)["timestamp"], ) - assert c.wrap_get_my_struct_BROKEN(Decimal("0.1")) == ( - Decimal("0.1"), + assert c.wrap_get_my_struct_BROKEN(decimal_to_int("0.1")) == ( + decimal_to_int("0.1"), w3.eth.get_block(w3.eth.block_number)["timestamp"], ) @@ -56,5 +56,5 @@ def wrap_get_my_struct_BROKEN(_e1: decimal) -> MyStruct: return self.get_my_struct(_e1) """ c = get_contract(code) - assert c.wrap_get_my_struct_WORKING(Decimal("0.1")) == (Decimal("0.1"),) - assert c.wrap_get_my_struct_BROKEN(Decimal("0.1")) == (Decimal("0.1"),) + assert c.wrap_get_my_struct_WORKING(decimal_to_int("0.1")) == (decimal_to_int("0.1"),) + assert c.wrap_get_my_struct_BROKEN(decimal_to_int("0.1")) == (decimal_to_int("0.1"),) diff --git a/tests/functional/codegen/features/iteration/test_break.py b/tests/functional/codegen/features/iteration/test_break.py index 4abde9c617..e939c2f0c6 100644 --- a/tests/functional/codegen/features/iteration/test_break.py +++ b/tests/functional/codegen/features/iteration/test_break.py @@ -1,7 +1,6 @@ -from decimal import Decimal - import pytest +from tests.utils import decimal_to_int from vyper.exceptions import StructureException @@ -21,10 +20,10 @@ def foo(n: decimal) -> int128: c = get_contract_with_gas_estimation(break_test) - assert c.foo(Decimal("1")) == 0 - assert c.foo(Decimal("2")) == 3 - assert c.foo(Decimal("10")) == 10 - assert c.foo(Decimal("200")) == 23 + assert c.foo(decimal_to_int("1")) == 0 + assert c.foo(decimal_to_int("2")) == 3 + assert c.foo(decimal_to_int("10")) == 10 + assert c.foo(decimal_to_int("200")) == 23 print("Passed for-loop break test") @@ -49,11 +48,11 @@ def foo(n: decimal) -> int128: """ c = get_contract_with_gas_estimation(break_test_2) - assert c.foo(Decimal("1")) == 0 - assert c.foo(Decimal("2")) == 3 - assert c.foo(Decimal("10")) == 10 - assert c.foo(Decimal("200")) == 23 - assert c.foo(Decimal("4000000")) == 66 + assert c.foo(decimal_to_int("1")) == 0 + assert c.foo(decimal_to_int("2")) == 3 + assert c.foo(decimal_to_int("10")) == 10 + assert c.foo(decimal_to_int("200")) == 23 + assert c.foo(decimal_to_int("4000000")) == 66 print("Passed for-loop break test 2") diff --git a/tests/functional/codegen/features/iteration/test_for_in_list.py b/tests/functional/codegen/features/iteration/test_for_in_list.py index cbf7c5c36c..6a017e92c1 100644 --- a/tests/functional/codegen/features/iteration/test_for_in_list.py +++ b/tests/functional/codegen/features/iteration/test_for_in_list.py @@ -1,8 +1,8 @@ import re -from decimal import Decimal import pytest +from tests.utils import decimal_to_int from vyper.compiler import compile_code from vyper.exceptions import ( ArgumentException, @@ -269,13 +269,13 @@ def i_return(break_count: int128) -> decimal: c = get_contract_with_gas_estimation(code) - c.set(0, Decimal("0.0001"), transact={}) - c.set(1, Decimal("1.1"), transact={}) - c.set(2, Decimal("2.2"), transact={}) + c.set(0, decimal_to_int("0.0001"), transact={}) + c.set(1, decimal_to_int("1.1"), transact={}) + c.set(2, decimal_to_int("2.2"), transact={}) - assert c.ret(2) == c.i_return(2) == Decimal("2.2") - assert c.ret(1) == c.i_return(1) == Decimal("1.1") - assert c.ret(0) == c.i_return(0) == Decimal("0.0001") + assert c.ret(2) == c.i_return(2) == decimal_to_int("2.2") + assert c.ret(1) == c.i_return(1) == decimal_to_int("1.1") + assert c.ret(0) == c.i_return(0) == decimal_to_int("0.0001") def test_for_in_list_iter_type(get_contract_with_gas_estimation): diff --git a/tests/functional/codegen/features/test_clampers.py b/tests/functional/codegen/features/test_clampers.py index fe51c026fe..5df8ac4970 100644 --- a/tests/functional/codegen/features/test_clampers.py +++ b/tests/functional/codegen/features/test_clampers.py @@ -4,6 +4,7 @@ from eth.codecs import abi from eth_utils import keccak +from tests.utils import decimal_to_int from vyper.exceptions import StackTooDeep from vyper.utils import int_bounds @@ -317,7 +318,7 @@ def foo(s: decimal) -> decimal: c = get_contract(code) - assert c.foo(Decimal(value)) == Decimal(value) + assert c.foo(decimal_to_int(value)) == decimal_to_int(value) @pytest.mark.parametrize( @@ -339,7 +340,7 @@ def foo(s: decimal) -> decimal: c = get_contract(code) with tx_failed(): - _make_tx(w3, c.address, "foo(fixed168x10)", [value]) + _make_tx(w3, c.address, "foo(int168)", [value]) @pytest.mark.parametrize("value", [0, 1, -1, 2**127 - 1, -(2**127)]) diff --git a/tests/functional/codegen/features/test_internal_call.py b/tests/functional/codegen/features/test_internal_call.py index eec740aa1b..ad89341fd9 100644 --- a/tests/functional/codegen/features/test_internal_call.py +++ b/tests/functional/codegen/features/test_internal_call.py @@ -1,10 +1,10 @@ import string -from decimal import Decimal import hypothesis.strategies as st import pytest from hypothesis import given, settings +from tests.utils import decimal_to_int from vyper.compiler import compile_code from vyper.exceptions import ArgumentException, CallViolation @@ -324,9 +324,9 @@ def bar6() -> int128: c = get_contract_with_gas_estimation(code) assert c.bar() == 0 - assert c.foo1([0, 0], Decimal("0")) == 0 + assert c.foo1([0, 0], decimal_to_int("0")) == 0 assert c.bar2() == 55 - assert c.bar3() == Decimal("1.33") + assert c.bar3() == decimal_to_int("1.33") assert c.bar4() == 77 assert c.bar5() == 88 @@ -351,7 +351,7 @@ def bar() -> (int128, decimal): return self._fooz(x, y, z, a), self._fooa(x, y, z, a) """ c = get_contract_with_gas_estimation(code) - assert c.bar() == [66, Decimal("66.77")] + assert c.bar() == [66, decimal_to_int("66.77")] def test_internal_function_multiple_lists_as_args(get_contract_with_gas_estimation): @@ -403,7 +403,7 @@ def bar() -> (Bytes[11], decimal, int128): return self._fooz(x, y, z, a), self._fooa(x, y, z, a), self._foox(x, y, z, a) """ c = get_contract_with_gas_estimation(code) - assert c.bar() == [b"hello world", Decimal("66.77"), 44] + assert c.bar() == [b"hello world", decimal_to_int("66.77"), 44] FAILING_CONTRACTS_CALL_VIOLATION = [ diff --git a/tests/functional/codegen/features/test_logging.py b/tests/functional/codegen/features/test_logging.py index 40f163d837..f8d30429a8 100644 --- a/tests/functional/codegen/features/test_logging.py +++ b/tests/functional/codegen/features/test_logging.py @@ -1,8 +1,7 @@ -from decimal import Decimal - import pytest from eth.codecs import abi +from tests.utils import decimal_to_int from vyper import compile_code from vyper.exceptions import ( ArgumentException, @@ -503,7 +502,7 @@ def foo(): logs1 = receipt["logs"][0] logs2 = receipt["logs"][1] event_id1 = keccak(bytes("MyLog(int128,bytes)", "utf-8")) - event_id2 = keccak(bytes("YourLog(address,(uint256,bytes,(string,fixed168x10)))", "utf-8")) + event_id2 = keccak(bytes("YourLog(address,(uint256,bytes,(string,int168)))", "utf-8")) # Event id is always the first topic assert logs1["topics"][0] == "0x" + event_id1.hex() @@ -533,7 +532,7 @@ def foo(): "type": "tuple", "components": [ {"name": "t", "type": "string"}, - {"name": "w", "type": "fixed168x10"}, + {"name": "w", "type": "int168", "internalType": "decimal"}, ], }, ], @@ -552,7 +551,7 @@ def foo(): logs = get_logs(tx_hash, c, "YourLog") args = logs[0].args assert args.arg1 == c.address - assert args.arg2 == {"x": 1, "y": b"abc", "z": {"t": "house", "w": Decimal("13.5")}} + assert args.arg2 == {"x": 1, "y": b"abc", "z": {"t": "house", "w": decimal_to_int("13.5")}} def test_fails_when_input_is_the_wrong_type(tx_failed, get_contract_with_gas_estimation): @@ -902,10 +901,10 @@ def foo(): tx_hash = c.foo(transact={}) logs = get_logs(tx_hash, c, "Bar") assert logs[0].args._value == [ - Decimal("1.11"), - Decimal("2.22"), - Decimal("3.33"), - Decimal("4.44"), + decimal_to_int("1.11"), + decimal_to_int("2.22"), + decimal_to_int("3.33"), + decimal_to_int("4.44"), ] @@ -954,15 +953,15 @@ def set_list(): tx_hash = c.foo(transact={}) logs = get_logs(tx_hash, c, "Bar") - assert logs[0].args._value == [Decimal("0"), Decimal("0"), Decimal("0"), Decimal("0")] + assert logs[0].args._value == [decimal_to_int("0")] * 4 c.set_list(transact={}) tx_hash = c.foo(transact={}) logs = get_logs(tx_hash, c, "Bar") assert logs[0].args._value == [ - Decimal("1.33"), - Decimal("2.33"), - Decimal("3.33"), - Decimal("4.33"), + decimal_to_int("1.33"), + decimal_to_int("2.33"), + decimal_to_int("3.33"), + decimal_to_int("4.33"), ] diff --git a/tests/functional/codegen/test_interfaces.py b/tests/functional/codegen/test_interfaces.py index e93cd5142b..aa70667c38 100644 --- a/tests/functional/codegen/test_interfaces.py +++ b/tests/functional/codegen/test_interfaces.py @@ -1,8 +1,8 @@ import json -from decimal import Decimal import pytest +from tests.utils import decimal_to_int from vyper.compiler import compile_code from vyper.exceptions import ( ArgumentException, @@ -246,7 +246,7 @@ def test(): ("min_value(int128)", "int128", -(2**127)), ("empty(uint8[2])", "uint8[2]", [0, 0]), ('method_id("vyper()", output_type=bytes4)', "bytes4", b"\x82\xcbE\xfb"), - ("epsilon(decimal)", "decimal", Decimal("1E-10")), + ("epsilon(decimal)", "decimal", decimal_to_int("1E-10")), ], ) def test_external_call_to_interface_kwarg(get_contract, kwarg, typ, expected, make_input_bundle): @@ -614,7 +614,7 @@ def bar(a: address) -> uint256: ("bool", True), ("address", "0x1234567890123456789012345678901234567890"), ("bytes32", b"bytes32bytes32bytes32bytes32poop"), - ("decimal", Decimal("3.1337")), + ("decimal", decimal_to_int("3.1337")), ("Bytes[4]", b"newp"), ("String[6]", "potato"), ] diff --git a/tests/functional/codegen/types/numbers/test_constants.py b/tests/functional/codegen/types/numbers/test_constants.py index af871983ab..8ad2b559de 100644 --- a/tests/functional/codegen/types/numbers/test_constants.py +++ b/tests/functional/codegen/types/numbers/test_constants.py @@ -1,8 +1,8 @@ import itertools -from decimal import Decimal import pytest +from tests.utils import decimal_to_int from vyper.compiler import compile_code from vyper.exceptions import TypeMismatch from vyper.utils import MemoryPositions @@ -59,15 +59,13 @@ def test_arithmetic(a: int128) -> int128: assert c.test_int128(-(2**127)) == [False, True] assert c.test_int128(0) == [False, False] - assert c.test_decimal(Decimal("18707220957835557353007165858768422651595.9365500927")) == [ - True, - False, - ] - assert c.test_decimal(Decimal("-18707220957835557353007165858768422651595.9365500928")) == [ - False, - True, - ] - assert c.test_decimal(Decimal("0.1")) == [False, False] + assert c.test_decimal( + decimal_to_int("18707220957835557353007165858768422651595.9365500927") + ) == [True, False] + assert c.test_decimal( + decimal_to_int("-18707220957835557353007165858768422651595.9365500928") + ) == [False, True] + assert c.test_decimal(decimal_to_int("0.1")) == [False, False] assert c.test_uint256(2**256 - 1) is True @@ -121,8 +119,8 @@ def zoo() -> uint256: assert c.joo() is None - assert c.koo() == Decimal(2**167 - 1) / 10**10 - assert c.loo() == Decimal(-(2**167)) / 10**10 + assert c.koo() == (2**167 - 1) + assert c.loo() == -(2**167) assert c.zoo() == 2**256 - 1 diff --git a/tests/functional/codegen/types/numbers/test_decimals.py b/tests/functional/codegen/types/numbers/test_decimals.py index 04cf267e4c..e66d2f001f 100644 --- a/tests/functional/codegen/types/numbers/test_decimals.py +++ b/tests/functional/codegen/types/numbers/test_decimals.py @@ -1,8 +1,9 @@ import warnings -from decimal import Decimal, getcontext +from decimal import getcontext import pytest +from tests.utils import decimal_to_int from vyper import compile_code from vyper.exceptions import ( DecimalOverrideException, @@ -149,13 +150,13 @@ def iarg() -> uint256: """ c = get_contract_with_gas_estimation(harder_decimal_test) - assert c.phooey(Decimal("1.2")) == Decimal("20736.0") - assert c.phooey(Decimal("-1.2")) == Decimal("20736.0") - assert c.arg(Decimal("-3.7")) == Decimal("-3.7") - assert c.arg(Decimal("3.7")) == Decimal("3.7") - assert c.garg() == Decimal("6.75") - assert c.harg() == Decimal("9.0") - assert c.iarg() == Decimal("14") + assert c.phooey(decimal_to_int("1.2")) == decimal_to_int("20736.0") + assert c.phooey(decimal_to_int("-1.2")) == decimal_to_int("20736.0") + assert c.arg(decimal_to_int("-3.7")) == decimal_to_int("-3.7") + assert c.arg(decimal_to_int("3.7")) == decimal_to_int("3.7") + assert c.garg() == decimal_to_int("6.75") + assert c.harg() == decimal_to_int("9.0") + assert c.iarg() == 14 print("Passed fractional multiplication test") @@ -171,8 +172,8 @@ def _num_mul(x: decimal, y: decimal) -> decimal: c = get_contract_with_gas_estimation(mul_code) - x = Decimal("85070591730234615865843651857942052864") - y = Decimal("136112946768375385385349842973") + x = decimal_to_int("85070591730234615865843651857942052864") + y = decimal_to_int("136112946768375385385349842973") with tx_failed(): c._num_mul(x, y) @@ -181,14 +182,18 @@ def _num_mul(x: decimal, y: decimal) -> decimal: y = 1 + DECIMAL_EPSILON with tx_failed(): - c._num_mul(x, y) + c._num_mul(decimal_to_int(x), decimal_to_int(y)) - assert c._num_mul(x, Decimal(1)) == x + assert c._num_mul(decimal_to_int(x), decimal_to_int(1)) == decimal_to_int(x) - assert c._num_mul(x, 1 - DECIMAL_EPSILON) == quantize(x * (1 - DECIMAL_EPSILON)) + assert c._num_mul(decimal_to_int(x), decimal_to_int(1 - DECIMAL_EPSILON)) == decimal_to_int( + quantize(x * (1 - DECIMAL_EPSILON)) + ) x = SizeLimits.MIN_AST_DECIMAL - assert c._num_mul(x, 1 - DECIMAL_EPSILON) == quantize(x * (1 - DECIMAL_EPSILON)) + assert c._num_mul(decimal_to_int(x), decimal_to_int(1 - DECIMAL_EPSILON)) == decimal_to_int( + quantize(x * (1 - DECIMAL_EPSILON)) + ) # division failure modes(!) @@ -205,35 +210,39 @@ def foo(x: decimal, y: decimal) -> decimal: y = -DECIMAL_EPSILON with tx_failed(): - c.foo(x, y) + c.foo(decimal_to_int(x), decimal_to_int(y)) with tx_failed(): - c.foo(x, Decimal(0)) + c.foo(decimal_to_int(x), 0) with tx_failed(): - c.foo(y, Decimal(0)) + c.foo(decimal_to_int(y), 0) - y = Decimal(1) - DECIMAL_EPSILON # 0.999999999 + y = 1 - DECIMAL_EPSILON # 0.999999999 with tx_failed(): - c.foo(x, y) + c.foo(decimal_to_int(x), decimal_to_int(y)) - y = Decimal(-1) + y = -1 with tx_failed(): - c.foo(x, y) + c.foo(decimal_to_int(x), decimal_to_int(y)) - assert c.foo(x, Decimal(1)) == x - assert c.foo(x, 1 + DECIMAL_EPSILON) == quantize(x / (1 + DECIMAL_EPSILON)) + assert c.foo(decimal_to_int(x), decimal_to_int(1)) == decimal_to_int(x) + assert c.foo(decimal_to_int(x), decimal_to_int(1 + DECIMAL_EPSILON)) == decimal_to_int( + quantize(x / (1 + DECIMAL_EPSILON)) + ) x = SizeLimits.MAX_AST_DECIMAL with tx_failed(): - c.foo(x, DECIMAL_EPSILON) + c.foo(decimal_to_int(x), decimal_to_int(DECIMAL_EPSILON)) - y = Decimal(1) - DECIMAL_EPSILON + y = 1 - DECIMAL_EPSILON with tx_failed(): - c.foo(x, y) + c.foo(decimal_to_int(x), decimal_to_int(y)) - assert c.foo(x, Decimal(1)) == x + assert c.foo(decimal_to_int(x), decimal_to_int(1)) == decimal_to_int(x) - assert c.foo(x, 1 + DECIMAL_EPSILON) == quantize(x / (1 + DECIMAL_EPSILON)) + assert c.foo(decimal_to_int(x), decimal_to_int(1 + DECIMAL_EPSILON)) == decimal_to_int( + quantize(x / (1 + DECIMAL_EPSILON)) + ) def test_decimal_min_max_literals(tx_failed, get_contract_with_gas_estimation): @@ -263,8 +272,8 @@ def bar(num: decimal) -> decimal: """ c = get_contract_with_gas_estimation(code) - assert c.foo() == Decimal("1e-10") # Smallest possible decimal - assert c.bar(Decimal("1e37")) == Decimal("-9e37") # Math lines up + assert c.foo() == decimal_to_int("1e-10") # Smallest possible decimal + assert c.bar(decimal_to_int("1e37")) == decimal_to_int("-9e37") # Math lines up def test_exponents(): diff --git a/tests/functional/codegen/types/numbers/test_modulo.py b/tests/functional/codegen/types/numbers/test_modulo.py index 465426cd1d..3b204e8121 100644 --- a/tests/functional/codegen/types/numbers/test_modulo.py +++ b/tests/functional/codegen/types/numbers/test_modulo.py @@ -1,7 +1,6 @@ -from decimal import Decimal - import pytest +from tests.utils import decimal_to_int from vyper.exceptions import ZeroDivisionException @@ -26,9 +25,9 @@ def num_modulo_decimal() -> decimal: """ c = get_contract_with_gas_estimation(code) assert c.num_modulo_num() == 1 - assert c.decimal_modulo_decimal() == Decimal(".18") - assert c.decimal_modulo_num() == Decimal(".5") - assert c.num_modulo_decimal() == Decimal(".5") + assert c.decimal_modulo_decimal() == decimal_to_int(".18") + assert c.decimal_modulo_num() == decimal_to_int(".5") + assert c.num_modulo_decimal() == decimal_to_int(".5") def test_modulo_with_input_of_zero(tx_failed, get_contract_with_gas_estimation): @@ -39,7 +38,7 @@ def foo(a: decimal, b: decimal) -> decimal: """ c = get_contract_with_gas_estimation(code) with tx_failed(): - c.foo(Decimal("1"), Decimal("0")) + c.foo(decimal_to_int("1"), decimal_to_int("0")) def test_literals_vs_evm(get_contract): diff --git a/tests/functional/codegen/types/numbers/test_sqrt.py b/tests/functional/codegen/types/numbers/test_sqrt.py index 020a79e7ef..94676404e8 100644 --- a/tests/functional/codegen/types/numbers/test_sqrt.py +++ b/tests/functional/codegen/types/numbers/test_sqrt.py @@ -4,6 +4,7 @@ import pytest from eth_tester.exceptions import TransactionFailed +from tests.utils import decimal_to_int from vyper.utils import SizeLimits DECIMAL_PLACES = 10 @@ -19,7 +20,7 @@ def decimal_truncate(val, decimal_places=DECIMAL_PLACES, rounding=ROUND_FLOOR): def decimal_sqrt(val): - return decimal_truncate(val.sqrt()) + return decimal_to_int(decimal_truncate(val.sqrt())) def test_sqrt_literal(get_contract_with_gas_estimation): @@ -32,6 +33,7 @@ def test() -> decimal: assert c.test() == decimal_sqrt(Decimal("2")) +# TODO: use parametrization here def test_sqrt_variable(get_contract_with_gas_estimation): code = """ @external @@ -47,12 +49,12 @@ def test2() -> decimal: c = get_contract_with_gas_estimation(code) val = Decimal("33.33") - assert c.test(val) == decimal_sqrt(val) + assert c.test(decimal_to_int(val)) == decimal_sqrt(val) val = Decimal("0.1") - assert c.test(val) == decimal_sqrt(val) + assert c.test(decimal_to_int(val)) == decimal_sqrt(val) - assert c.test(Decimal("0.0")) == Decimal("0.0") + assert c.test(decimal_to_int("0.0")) == decimal_to_int("0.0") assert c.test2() == decimal_sqrt(Decimal("44.001")) @@ -73,9 +75,9 @@ def test2() -> decimal: c = get_contract_with_gas_estimation(code) val = Decimal("12.21") - assert c.test(val) == decimal_sqrt(val + 1) + assert c.test(decimal_to_int(val)) == decimal_sqrt(val + 1) val = Decimal("100.01") - assert c.test(val) == decimal_sqrt(val + 1) + assert c.test(decimal_to_int(val)) == decimal_sqrt(val + 1) assert c.test2() == decimal_sqrt(Decimal("444.44")) @@ -94,11 +96,11 @@ def test(a: decimal) -> (decimal, decimal, decimal, decimal, decimal, String[100 c = get_contract_with_gas_estimation(code) val = Decimal("2.1") - assert c.test(val) == [ - val, - Decimal("1"), - Decimal("2"), - Decimal("3"), + assert c.test(decimal_to_int(val)) == [ + decimal_to_int(val), + decimal_to_int("1"), + decimal_to_int("2"), + decimal_to_int("3"), decimal_sqrt(val), "hello world", ] @@ -114,7 +116,7 @@ def test(a: decimal) -> decimal: c = get_contract(code) - vyper_sqrt = c.test(value) + vyper_sqrt = c.test(decimal_to_int(value)) actual_sqrt = decimal_sqrt(value) assert vyper_sqrt == actual_sqrt @@ -132,7 +134,7 @@ def test(a: decimal) -> decimal: @pytest.mark.parametrize("value", [Decimal(0), Decimal(SizeLimits.MAX_INT128)]) def test_sqrt_bounds(sqrt_contract, value): - vyper_sqrt = sqrt_contract.test(value) + vyper_sqrt = sqrt_contract.test(decimal_to_int(value)) actual_sqrt = decimal_sqrt(value) assert vyper_sqrt == actual_sqrt @@ -146,7 +148,7 @@ def test_sqrt_bounds(sqrt_contract, value): @hypothesis.example(value=Decimal(SizeLimits.MAX_INT128)) @hypothesis.example(value=Decimal(0)) def test_sqrt_valid_range(sqrt_contract, value): - vyper_sqrt = sqrt_contract.test(value) + vyper_sqrt = sqrt_contract.test(decimal_to_int(value)) actual_sqrt = decimal_sqrt(value) assert vyper_sqrt == actual_sqrt @@ -161,4 +163,4 @@ def test_sqrt_valid_range(sqrt_contract, value): @hypothesis.example(value=Decimal("-1E10")) def test_sqrt_invalid_range(sqrt_contract, value): with pytest.raises(TransactionFailed): - sqrt_contract.test(value) + sqrt_contract.test(decimal_to_int(value)) diff --git a/tests/functional/codegen/types/test_dynamic_array.py b/tests/functional/codegen/types/test_dynamic_array.py index efa2799480..63d3d0b8f1 100644 --- a/tests/functional/codegen/types/test_dynamic_array.py +++ b/tests/functional/codegen/types/test_dynamic_array.py @@ -2,6 +2,7 @@ import pytest +from tests.utils import decimal_to_int from vyper.compiler import compile_code from vyper.exceptions import ( ArgumentException, @@ -220,7 +221,10 @@ def uoo(inp: DynArray[Foobar, 2]) -> DynArray[DynArray[Foobar, 2], 2]: assert c.poo([]) == [] assert c.poo([[1, 2], [3, 4]]) == [[1, 2], [3, 4]] assert c.qoo([1, 2]) == [[1, 2], [3, 4]] - assert c.roo([1, 2]) == [[1.0, 2.0], [3.0, 4.0]] + assert c.roo([decimal_to_int(1), decimal_to_int(2)]) == [ + [decimal_to_int(1), decimal_to_int(2)], + [decimal_to_int(3), decimal_to_int(4)], + ] assert c.soo() == [1, 2] assert c.too() == [2, 1] assert c.uoo([1, 2]) == [[1, 2], [2, 1]] @@ -729,9 +733,15 @@ def test_array_decimal_return3() -> DynArray[DynArray[decimal, 2], 2]: c = get_contract_with_gas_estimation(code) assert c.test_array_num_return() == [[], [3, 4]] - assert c.test_array_decimal_return1() == [[1.0], [3.0, 4.0]] - assert c.test_array_decimal_return2() == [[1.0, 2.0]] - assert c.test_array_decimal_return3() == [[1.0, 2.0], [3.0]] + assert c.test_array_decimal_return1() == [ + [decimal_to_int(1)], + [decimal_to_int(3), decimal_to_int(4)], + ] + assert c.test_array_decimal_return2() == [[decimal_to_int(1), decimal_to_int(2)]] + assert c.test_array_decimal_return3() == [ + [decimal_to_int(1), decimal_to_int(2)], + [decimal_to_int(3)], + ] @pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") @@ -1660,7 +1670,7 @@ def ix(i: uint256) -> decimal: """ c = get_contract(code) for i, p in enumerate(some_good_primes): - assert c.ix(i) == p + assert c.ix(i) == decimal_to_int(p) # assert oob with tx_failed(): c.ix(len(some_good_primes) + 1) diff --git a/tests/functional/codegen/types/test_lists.py b/tests/functional/codegen/types/test_lists.py index ee287064e8..9793ddbc38 100644 --- a/tests/functional/codegen/types/test_lists.py +++ b/tests/functional/codegen/types/test_lists.py @@ -2,9 +2,20 @@ import pytest +from tests.utils import decimal_to_int from vyper.exceptions import ArrayIndexException, OverflowException, TypeMismatch +def _map_nested(f, xs): + ret = [] + for x in xs: + if isinstance(x, list): + ret.append(_map_nested(f, x)) + else: + ret.append(f(x)) + return ret + + def test_list_tester_code(get_contract_with_gas_estimation): list_tester_code = """ z: int128[3] @@ -118,9 +129,9 @@ def roo(inp: decimal[2]) -> decimal[2][2]: assert c.noo([3, 5]) == [3, 5] assert c.poo([[1, 2], [3, 4]]) == [[1, 2], [3, 4]] assert c.qoo([1, 2]) == [[1, 2], [3, 4]] - assert c.roo([1, 2]) == [[1.0, 2.0], [3.0, 4.0]] - - print("Passed list output tests") + assert c.roo(_map_nested(decimal_to_int, [1.0, 2.0])) == _map_nested( + decimal_to_int, [[1.0, 2.0], [3.0, 4.0]] + ) def test_array_accessor(get_contract_with_gas_estimation): @@ -325,9 +336,9 @@ def test_array_decimal_return3() -> decimal[2][2]: c = get_contract_with_gas_estimation(code) assert c.test_array_num_return() == [[1, 2], [3, 4]] - assert c.test_array_decimal_return1() == [[1.0, 2.0], [3.0, 4.0]] - assert c.test_array_decimal_return2() == [[1.0, 2.0], [3.0, 4.0]] - assert c.test_array_decimal_return3() == [[1.0, 2.0], [3.0, 4.0]] + assert c.test_array_decimal_return1() == _map_nested(decimal_to_int, [[1.0, 2.0], [3.0, 4.0]]) + assert c.test_array_decimal_return2() == _map_nested(decimal_to_int, [[1.0, 2.0], [3.0, 4.0]]) + assert c.test_array_decimal_return3() == _map_nested(decimal_to_int, [[1.0, 2.0], [3.0, 4.0]]) def test_mult_list(get_contract_with_gas_estimation): @@ -674,6 +685,9 @@ def ix(i: uint256) -> {type}: """ c = get_contract(code) for i, p in enumerate(value): + if type == "decimal": + # special case to transform for decimal ABI + p = decimal_to_int(p) assert c.ix(i) == p # assert oob with tx_failed(): @@ -809,6 +823,9 @@ def ix(i: uint256, j: uint256) -> {type}: c = get_contract(code) for i, p in enumerate(value): for j, q in enumerate(p): + if type == "decimal": + # special case for decimal ABI + q = decimal_to_int(q) assert c.ix(i, j) == q # assert oob with tx_failed(): diff --git a/tests/unit/abi_types/test_invalid_abi_types.py b/tests/unit/abi_types/test_invalid_abi_types.py index 1a8a7db884..ad4c8f7390 100644 --- a/tests/unit/abi_types/test_invalid_abi_types.py +++ b/tests/unit/abi_types/test_invalid_abi_types.py @@ -1,18 +1,10 @@ import pytest -from vyper.abi_types import ( - ABI_Bytes, - ABI_BytesM, - ABI_DynamicArray, - ABI_FixedMxN, - ABI_GIntM, - ABI_String, -) +from vyper.abi_types import ABI_Bytes, ABI_BytesM, ABI_DynamicArray, ABI_GIntM, ABI_String from vyper.exceptions import InvalidABIType cases_invalid_types = [ (ABI_GIntM, ((0, False), (7, False), (300, True), (300, False))), - (ABI_FixedMxN, ((0, 0, False), (8, 0, False), (256, 81, True), (300, 80, False))), (ABI_BytesM, ((0,), (33,), (-10,))), (ABI_Bytes, ((-1,), (-69,))), (ABI_DynamicArray, ((ABI_GIntM(256, False), -1), (ABI_String(256), -10))), diff --git a/tests/unit/ast/nodes/test_fold_binop_decimal.py b/tests/unit/ast/nodes/test_fold_binop_decimal.py index 2e686818ab..cd3fb2ae8f 100644 --- a/tests/unit/ast/nodes/test_fold_binop_decimal.py +++ b/tests/unit/ast/nodes/test_fold_binop_decimal.py @@ -4,7 +4,7 @@ from hypothesis import example, given, settings from hypothesis import strategies as st -from tests.utils import parse_and_fold +from tests.utils import decimal_to_int, parse_and_fold from vyper.exceptions import OverflowException, TypeMismatch, ZeroDivisionException from vyper.semantics.analysis.local import ExprVisitor from vyper.semantics.types import DecimalT @@ -48,8 +48,10 @@ def foo(a: decimal, b: decimal) -> decimal: except (OverflowException, ZeroDivisionException): is_valid = False + left = decimal_to_int(left) + right = decimal_to_int(right) if is_valid: - assert contract.foo(left, right) == new_node.value + assert contract.foo(left, right) == decimal_to_int(new_node.value) else: with tx_failed(): contract.foo(left, right) @@ -95,8 +97,9 @@ def foo({input_value}) -> decimal: # for overflow or division/modulus by 0, expect the contract call to revert is_valid = False + values = [decimal_to_int(v) for v in values] if is_valid: - assert contract.foo(*values) == expected + assert contract.foo(*values) == decimal_to_int(expected) else: with tx_failed(): contract.foo(*values) diff --git a/tests/unit/compiler/test_abi.py b/tests/unit/compiler/test_abi.py index f3274f91a5..430cd75344 100644 --- a/tests/unit/compiler/test_abi.py +++ b/tests/unit/compiler/test_abi.py @@ -58,6 +58,39 @@ def foo(y: uint256) -> Bytes[100]: assert out["method_identifiers"] == {"foo(uint256)": "0x2fbebd38", "x()": "0xc55699c"} +def test_decimal_abi(): + code = """ +x: public(decimal) + +@external +@view +def foo(s: decimal) -> decimal: + return s + """ + + out = compile_code(code, output_formats=["method_identifiers", "abi"]) + + assert out["method_identifiers"] == {"foo(int168)": "0x929893b6", "x()": "0xc55699c"} + + expected_abi = [ + { + "stateMutability": "view", + "type": "function", + "name": "foo", + "inputs": [{"name": "s", "type": "int168", "internalType": "decimal"}], + "outputs": [{"name": "", "type": "int168", "internalType": "decimal"}], + }, + { + "stateMutability": "view", + "type": "function", + "name": "x", + "inputs": [], + "outputs": [{"name": "", "type": "int168", "internalType": "decimal"}], + }, + ] + assert out["abi"] == expected_abi + + def test_struct_abi(): code = """ struct MyStruct: diff --git a/tests/unit/semantics/types/test_event.py b/tests/unit/semantics/types/test_event.py index c2283bfe25..1c41ab8a4a 100644 --- a/tests/unit/semantics/types/test_event.py +++ b/tests/unit/semantics/types/test_event.py @@ -1,7 +1,14 @@ import pytest from vyper.semantics.types.user import EventT +from vyper.utils import keccak256 + +def keccak256_int(signature_str): + return int.from_bytes(keccak256(signature_str.encode()), "big") + + +# TODO: refactor these to all use keccak256_int util instead of hardcoded constants EVENT_ID_TESTS = [ ( "event MyLog: pass", @@ -72,8 +79,7 @@ ( """event Bar: a: decimal[4]""", - # Bar(fixed168x10[4]) - 0x7F5D3D77DC11EED2D256D513EF1916FBA342AD13DD629E3C2FF3BD1BAEADF932, + keccak256_int("Bar(int168[4])"), ), ( """event Rtadr: @@ -81,8 +87,7 @@ b: decimal[2][5] c: Bytes[4] d: decimal[666]""", - # Rtadr(fixed168x10,fixed168x10[2][5],bytes,fixed168x10[666]) - 0x20B4E04949A8E3B03C8DECC09D8B18B271D42E66F83B9FAA7B75EA7E22E27177, + keccak256_int("Rtadr(int168,int168[2][5],bytes,int168[666])"), ), ] diff --git a/tests/utils.py b/tests/utils.py index 25dad818ca..badbb2545e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,8 +1,10 @@ import contextlib +import decimal import os from vyper import ast as vy_ast from vyper.semantics.analysis.constant_folding import constant_fold +from vyper.utils import DECIMAL_EPSILON, round_towards_zero @contextlib.contextmanager @@ -19,3 +21,8 @@ def parse_and_fold(source_code): ast = vy_ast.parse_to_ast(source_code) constant_fold(ast) return ast + + +def decimal_to_int(*args): + s = decimal.Decimal(*args) + return round_towards_zero(s / DECIMAL_EPSILON) diff --git a/vyper/abi_types.py b/vyper/abi_types.py index 051f8db19f..24d6fe866a 100644 --- a/vyper/abi_types.py +++ b/vyper/abi_types.py @@ -109,35 +109,6 @@ def selector_name(self): return "bool" -# fixedx: signed fixed-point decimal number of M bits, 8 <= M <= 256, -# M % 8 ==0, and 0 < N <= 80, which denotes the value v as v / (10 ** N). -# ufixedx: unsigned variant of fixedx. -# fixed, ufixed: synonyms for fixed128x18, ufixed128x18 respectively. -# For computing the function selector, fixed128x18 and ufixed128x18 have to be used. -class ABI_FixedMxN(ABIType): - def __init__(self, m_bits, n_places, signed): - if not (0 < m_bits <= 256 and 0 == m_bits % 8): - raise InvalidABIType("Invalid M for FixedMxN") - if not (0 < n_places and n_places <= 80): - raise InvalidABIType("Invalid N for FixedMxN") - - self.m_bits = m_bits - self.n_places = n_places - self.signed = signed - - def is_dynamic(self): - return False - - def static_size(self): - return 32 - - def selector_name(self): - return ("" if self.signed else "u") + f"fixed{self.m_bits}x{self.n_places}" - - def is_complex_type(self): - return False - - # bytes: binary type of M bytes, 0 < M <= 32. class ABI_BytesM(ABIType): def __init__(self, m_bytes): diff --git a/vyper/semantics/types/base.py b/vyper/semantics/types/base.py index 46edb522ca..910281fff7 100644 --- a/vyper/semantics/types/base.py +++ b/vyper/semantics/types/base.py @@ -196,7 +196,7 @@ def canonical_abi_type(self) -> str: """ return self.abi_type.selector_name() - def to_abi_arg(self, name: str = "") -> Dict[str, Any]: + def to_abi_arg(self, name: str = "") -> dict[str, Any]: """ The JSON ABI description of this type. Note for complex types, the implementation is overridden to be compliant with the spec: diff --git a/vyper/semantics/types/primitives.py b/vyper/semantics/types/primitives.py index e3a5d7f834..4e8af80aac 100644 --- a/vyper/semantics/types/primitives.py +++ b/vyper/semantics/types/primitives.py @@ -2,10 +2,10 @@ from decimal import Decimal from functools import cached_property -from typing import Tuple, Union +from typing import Any, Tuple, Union from vyper import ast as vy_ast -from vyper.abi_types import ABI_Address, ABI_Bool, ABI_BytesM, ABI_FixedMxN, ABI_GIntM, ABIType +from vyper.abi_types import ABI_Address, ABI_Bool, ABI_BytesM, ABI_GIntM, ABIType from vyper.exceptions import ( CompilerPanic, InvalidLiteral, @@ -339,7 +339,7 @@ def validate_numeric_op(self, node) -> None: @cached_property def abi_type(self) -> ABIType: - return ABI_FixedMxN(self._bits, self._decimal_places, self._is_signed) + return ABI_GIntM(self._bits, self._is_signed) @cached_property def decimals(self) -> int: @@ -364,6 +364,11 @@ def decimal_bounds(self) -> Tuple[Decimal, Decimal]: DIVISOR = Decimal(self.divisor) return lo / DIVISOR, hi / DIVISOR + def to_abi_arg(self, name: str = "") -> dict[str, Any]: + ret = super().to_abi_arg(name) + ret["internalType"] = repr(self) + return ret + # maybe this even deserves its own module, address.py # should inherit from uint160? diff --git a/vyper/semantics/types/utils.py b/vyper/semantics/types/utils.py index 0546668900..93cf85d5f8 100644 --- a/vyper/semantics/types/utils.py +++ b/vyper/semantics/types/utils.py @@ -1,5 +1,3 @@ -from typing import Dict - from vyper import ast as vy_ast from vyper.exceptions import ( ArrayIndexException, @@ -18,13 +16,13 @@ # TODO maybe this should be merged with .types/base.py -def type_from_abi(abi_type: Dict) -> VyperType: +def type_from_abi(abi_type: dict) -> VyperType: """ Return a type object from an ABI type definition. Arguments --------- - abi_type : Dict + abi_type : dict A type definition taken from the `input` or `output` field of an ABI. Returns @@ -33,7 +31,7 @@ def type_from_abi(abi_type: Dict) -> VyperType: Type definition object. """ type_string = abi_type["type"] - if type_string == "fixed168x10": + if type_string == "int168" and abi_type.get("internalType") == "decimal": type_string = "decimal" if type_string in ("string", "bytes"): type_string = type_string.capitalize()