Repository: ethereum/web3.py

Issue:
Add contract test with fixedMxN argument and return value
### What was wrong?

`fixedMxN` types are not supported. Various groups (including Casper), need them.

### How can it be fixed?

#828 only fixed a specific issue raised by a team that wants to use fixed types. It is likely that there are other things that need to be addressed before everything is resolved. The best way to know is to add a new contract in the tests that accepts and returns a fixed type, and to test it out.


Discussion:
This is going to take longer than I thought: [fixed types are not supported by Solidity, as of v0.4.23](http://solidity.readthedocs.io/en/v0.4.23/types.html#fixed-point-numbers).

Time to go learn me some Vyper.

Edit~ not supported in Vyper yet either. :( [Extended conversation in chat](https://gitter.im/ethereum/web3.py?at=5af32b28f88365153c0715ee)...

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -19,7 +19,7 @@
     install_requires=[
         "toolz>=0.9.0,<1.0.0;implementation_name=='pypy'",
         "cytoolz>=0.9.0,<1.0.0;implementation_name=='cpython'",
-        "eth-abi>=1.1.0,<2",
+        "eth-abi>=1.1.1,<2",
         "eth-account>=0.2.1,<0.3.0",
         "eth-utils>=1.0.1,<2.0.0",
         "hexbytes>=0.1.0,<1.0.0",
diff --git a/web3/utils/abi.py b/web3/utils/abi.py
--- a/web3/utils/abi.py
+++ b/web3/utils/abi.py
@@ -264,9 +264,9 @@ def get_constructor_abi(contract_abi):
 ))
 
 SUB_TYPE_REGEX = (
-    '\['
+    r'\['
     '[0-9]*'
-    '\]'
+    r'\]'
 )
 
 TYPE_REGEX = (
@@ -322,7 +322,7 @@ def size_of_type(abi_type):
         return 8
     if abi_type == 'address':
         return 160
-    return int(re.sub("\D", "", abi_type))
+    return int(re.sub(r"\D", "", abi_type))
 
 
 END_BRACKETS_OF_ARRAY_TYPE_REGEX = r"\[[^]]*\]$"
@@ -371,7 +371,7 @@ def is_array_type(abi_type):
 ENUM_REGEX = (
     '^'
     '{lib_name}'
-    '\.'
+    r'\.'
     '{enum_name}'
     '$'
 ).format(lib_name=NAME_REGEX, enum_name=NAME_REGEX)
diff --git a/web3/utils/contracts.py b/web3/utils/contracts.py
--- a/web3/utils/contracts.py
+++ b/web3/utils/contracts.py
@@ -105,7 +105,7 @@ def find_matching_fn_abi(abi, fn_identifier=None, args=None, kwargs=None):
         if arg_count_matches == 0:
             diagnosis = "\nFunction invocation failed due to improper number of arguments."
         elif encoding_matches == 0:
-            diagnosis = "\nFunction invocation failed due to improper argument encoding."
+            diagnosis = "\nFunction invocation failed due to no matching argument types."
         elif encoding_matches > 1:
             diagnosis = (
                 "\nAmbiguous argument encoding. "


Test patch:
diff --git a/tests/core/contracts/conftest.py b/tests/core/contracts/conftest.py
--- a/tests/core/contracts/conftest.py
+++ b/tests/core/contracts/conftest.py
@@ -432,6 +432,51 @@ def FallballFunctionContract(web3, FALLBACK_FUNCTION_CONTRACT):
     return web3.eth.contract(**FALLBACK_FUNCTION_CONTRACT)
 
 
+# no matter the function selector, this will return back the 32 bytes of data supplied
+CONTRACT_REFLECTION_CODE = (
+    "0x610011566020600460003760206000f3005b61000461001103610004600039610004610011036000f3"
+)
+
+# reference source used to generate it:
+LLL_SOURCE = "['seq', ['return', 0, ['lll', ['seq', ['calldatacopy', 0, 4, 32], ['return', 0, 32], 'stop' ], 0]]])"  # noqa: E501
+
+CONTRACT_FIXED_ABI = [
+    {
+        "type": "function",
+        "constant": False,
+        "inputs": [{"type": "fixed8x1"}],
+        "name": "reflect",
+        "outputs": [{"type": "fixed8x1"}],
+    },
+    {
+        "type": "function",
+        "constant": False,
+        "inputs": [{"type": "ufixed256x80"}],
+        "name": "reflect",
+        "outputs": [{"type": "ufixed256x80"}],
+    },
+    {
+        "type": "function",
+        "constant": False,
+        "inputs": [{"type": "ufixed256x1"}],
+        "name": "reflect",
+        "outputs": [{"type": "ufixed256x1"}],
+    },
+    {
+        "type": "function",
+        "constant": False,
+        "inputs": [{"type": "ufixed8x1"}],
+        "name": "reflect_short_u",
+        "outputs": [{"type": "ufixed8x1"}],
+    },
+]
+
+
+@pytest.fixture
+def FixedReflectionContract(web3):
+    return web3.eth.contract(abi=CONTRACT_FIXED_ABI, bytecode=CONTRACT_REFLECTION_CODE)
+
+
 class LogFunctions:
     LogAnonymous = 0
     LogNoArguments = 1
diff --git a/tests/core/contracts/test_contract_call_interface.py b/tests/core/contracts/test_contract_call_interface.py
--- a/tests/core/contracts/test_contract_call_interface.py
+++ b/tests/core/contracts/test_contract_call_interface.py
@@ -1,3 +1,7 @@
+from decimal import (
+    Decimal,
+    getcontext,
+)
 import json
 import pytest
 
@@ -69,6 +73,11 @@ def bytes_contract(web3, BytesContract, request):
     return deploy(web3, BytesContract, args=[request.param])
 
 
+@pytest.fixture()
+def fixed_reflection_contract(web3, FixedReflectionContract):
+    return deploy(web3, FixedReflectionContract)
+
+
 @pytest.fixture()
 def call_transaction():
     return {
@@ -440,7 +449,7 @@ def test_returns_data_from_specified_block(web3, math_contract):
     r"\nFunction invocation failed due to improper number of arguments."
 )
 diagnosis_encoding_regex = (
-    r"\nFunction invocation failed due to improper argument encoding."
+    r"\nFunction invocation failed due to no matching argument types."
 )
 diagnosis_ambiguous_encoding = (
     r"\nAmbiguous argument encoding. "
@@ -499,3 +508,66 @@ def test_call_abi_no_functions(web3):
     contract = web3.eth.contract(abi=[])
     with pytest.raises(NoABIFunctionsFound):
         contract.functions.thisFunctionDoesNotExist().call()
+
+
+@pytest.mark.parametrize(
+    'function, value',
+    (
+        # minimum positive unambiguous value (larger than fixed8x1)
+        ('reflect', Decimal('12.8')),
+        # maximum value (for ufixed256x1)
+        ('reflect', Decimal(2 ** 256 - 1) / 10),
+        # maximum negative unambiguous value (less than 0 from ufixed*)
+        ('reflect', Decimal('-0.1')),
+        # minimum value (for fixed8x1)
+        ('reflect', Decimal('-12.8')),
+        # only ufixed256x80 type supports 2-80 decimals
+        ('reflect', Decimal(2 ** 256 - 1) / 10 ** 80),  # maximum allowed value
+        ('reflect', Decimal(1) / 10 ** 80),  # smallest non-zero value
+        # minimum value (for ufixed8x1)
+        ('reflect_short_u', 0),
+        # maximum value (for ufixed8x1)
+        ('reflect_short_u', Decimal('25.5')),
+    ),
+)
+def test_reflect_fixed_value(web3, fixed_reflection_contract, function, value):
+    contract_func = fixed_reflection_contract.functions[function]
+    reflected = contract_func(value).call({'gas': 420000})
+    assert reflected == value
+
+
+DEFAULT_DECIMALS = getcontext().prec
+
+
+@pytest.mark.parametrize(
+    'function, value, error',
+    (
+        # out of range
+        ('reflect_short_u', Decimal('25.6'), "no matching argument types"),
+        ('reflect_short_u', Decimal('-.1'), "no matching argument types"),
+        # too many digits for *x1, too large for 256x80
+        ('reflect', Decimal('0.01'), "no matching argument types"),
+
+        # too many digits
+        ('reflect_short_u', Decimal('0.01'), "no matching argument types"),
+        (
+            'reflect_short_u',
+            Decimal('1e-%d' % (DEFAULT_DECIMALS + 1)),
+            "no matching argument types",
+        ),
+        ('reflect_short_u', Decimal('25.4' + '9' * DEFAULT_DECIMALS), "no matching argument types"),
+        ('reflect', Decimal(1) / 10 ** 81, "no matching argument types"),
+
+        # floats not accepted, for floating point error concerns
+        ('reflect_short_u', 0.1, "no matching argument types"),
+
+        # ambiguous
+        ('reflect', Decimal('12.7'), "Ambiguous argument encoding"),
+        ('reflect', Decimal(0), "Ambiguous argument encoding"),
+        ('reflect', 0, "Ambiguous argument encoding"),
+    ),
+)
+def test_invalid_fixed_value_reflections(web3, fixed_reflection_contract, function, value, error):
+    contract_func = fixed_reflection_contract.functions[function]
+    with pytest.raises(ValidationError, match=error):
+        contract_func(value).call({'gas': 420000})