Repository: vyperlang/vyper

Issue:
uint256 doesn't work as array index
### Version Information

* vyper Version (output of `vyper --version`): 0.1.0b10
* OS: linux Kubuntu 14.04 Linux satriani 4.9.119-first-custom #1 SMP Mon Aug 13 18:33:19 +07 2018 x86_64 x86_64 x86_64 GNU/Linux
* Python Version (output of `python --version`): 3.6.3
* Environment (output of `pip freeze`):
asn1crypto==0.24.0
async-generator==1.10
async-lru==0.1.0
asyncio-cancel-token==0.1.0a2
atomicwrites==1.3.0
attrdict==2.0.1
attrs==19.1.0
backcall==0.1.0
base58==1.0.3
bloom-filter==1.3
cached-property==1.5.1
cachetools==3.1.1
certifi==2019.3.9
cffi==1.12.3
chardet==3.0.4
coincurve==10.0.0
cryptography==2.7
cypari==2.3.0
cytoolz==0.9.0.1
decorator==4.4.0
eth-abi==2.0.0b9
eth-account==0.4.0
eth-bloom==1.0.3
eth-hash==0.2.0
eth-keyfile==0.5.1
eth-keys==0.2.4
eth-rlp==0.1.2
eth-tester==0.1.0b39
eth-typing==2.1.0
eth-utils==1.6.1
ethpm==0.1.4a19
future==0.17.1
FXrays==1.3.3
hexbytes==0.2.0
idna==2.8
ifaddr==0.1.6
importlib-metadata==0.18
ipdb==0.12
ipfshttpclient==0.4.12
ipython==7.5.0
ipython-genutils==0.2.0
jedi==0.13.3
jsonschema==2.6.0
lahja==0.12.0
lru-dict==1.1.6
lxml==4.3.3
more-itertools==7.0.0
multiaddr==0.0.8
mypy-extensions==0.4.1
netaddr==0.7.19
netdisco==2.6.0
networkx==2.3
packaging==19.0
parsimonious==0.8.1
parso==0.4.0
pexpect==4.7.0
pickleshare==0.7.5
pipenv==2018.11.26
plink==2.2
pluggy==0.12.0
plyvel==1.0.5
prompt-toolkit==2.0.9
protobuf==3.8.0
ptyprocess==0.6.0
py==1.8.0
py-ecc==1.7.0
py-evm==0.2.0a42
py-geth==2.1.0
pycparser==2.19
pycryptodome==3.8.2
pyethash==0.1.27
Pygments==2.4.2
pyparsing==2.4.0
pypng==0.0.19
pyrsistent==0.15.2
pysha3==1.0.2
pytest==4.6.3
pytest-ethereum==0.1.3a6
python-dateutil==2.8.0
requests==2.22.0
rlp==1.1.0
semantic-version==2.6.0
simplegeneric==0.8.1
six==1.12.0
snappy-manifolds==1.0
spherogram==1.8.1
SQLAlchemy==1.3.4
toolz==0.9.0
traitlets==4.3.2
trie==1.4.0
typing-extensions==3.7.2
uPnPClient==0.0.8
urllib3==1.25.3
uvloop==0.11.2
varint==1.0.2
virtualenv==16.6.0
virtualenv-clone==0.5.3
vyper==0.1.0b10
wcwidth==0.1.7
web3==5.0.0b2
websockets==7.0
zeroconf==0.22.0
zipp==0.5.1



### What's your issue about?

Attempts to use uint256 as an index parameter into an array results in the following error:

vyper.exceptions.TypeMismatchException: line 207:8 Typecasting from base type uint256 to int128 unavailable

Example code:
```
MAX_AUCTION_ITEMS: constant(uint256)  = 5

tokenIds: uint256[MAX_AUCTION_ITEMS]
tokenQty: uint256 # Total number of tokenIds actually in the tokenIds list.



@public
def __init__(_token_ids: uint256[MAX_AUCTION_ITEMS], _kyc_provider: address, _start: timestamp, _end: timestamp):
    """
    _token_ids - a list of each UNIQUE tokenId in order of most valuable
    being the first in the list.

    _kyc_provider - wallet address of the account with rights to manage
    the approved bidder list.

    _start - the datetime when the auction can accept bids.

    _end - the datetime when the auction ends normal bidding activities.
    """

    # load up the tokenIds
    for tokenId in _token_ids:
        if tokenId == 0: break


        self.tokenIds[self.tokenQty] = tokenId ### vyper.exceptions.TypeMismatchException: line 207:8 Typecasting from base type uint256 to int128 unavailable

        
        self.tokenQty += 1    
```


### How can it be fixed?

@charles-cooper figured out that the following work around get's us by:

self.tokenIds[convert(self.tokenQty, int128)] = tokenId




Discussion:

Resolving patch:
diff --git a/vyper/exceptions.py b/vyper/exceptions.py
--- a/vyper/exceptions.py
+++ b/vyper/exceptions.py
@@ -88,6 +88,10 @@ class SyntaxException(ParserException):
     pass
 
 
+class ArrayIndexException(ParserException):
+    pass
+
+
 class CompilerPanic(Exception):
 
     def __init__(self, message):
diff --git a/vyper/parser/parser_utils.py b/vyper/parser/parser_utils.py
--- a/vyper/parser/parser_utils.py
+++ b/vyper/parser/parser_utils.py
@@ -8,6 +8,7 @@
 
 from vyper import ast
 from vyper.exceptions import (
+    ArrayIndexException,
     InvalidLiteralException,
     StructureException,
     TypeMismatchException,
@@ -333,9 +334,24 @@ def add_variable_offset(parent, key, pos):
     elif isinstance(typ, ListType):
 
         subtype = typ.subtype
-        sub = [
-            'uclamplt', base_type_conversion(key, key.typ, BaseType('int128'), pos=pos), typ.count
-        ]
+        k = unwrap_location(key)
+        if not is_base_type(key.typ, ('int128', 'uint256')):
+            raise TypeMismatchException('Invalid type for array index: %r' % key.typ, pos)
+
+        if key.typ.is_literal:  # note: BaseType always has is_literal attr
+            # perform the check at compile time and elide the runtime check.
+            if key.value < 0 or key.value >= typ.count:
+                raise ArrayIndexException(
+                        'Array index determined to be out of bounds. '
+                        'Index is %r but array size is %r' % (key.value, typ.count),
+                        pos)
+            sub = k
+        else:
+            # this works, even for int128. for int128, since two's-complement
+            # is used, if the index is negative, (unsigned) LT will interpret
+            # it as a very large number, larger than any practical value for
+            # an array index, and the clamp will throw an error.
+            sub = ['uclamplt', k, typ.count]
 
         if location == 'storage':
             return LLLnode.from_list(['add', ['sha3_32', parent], sub],


Test patch:
diff --git a/tests/parser/types/test_lists.py b/tests/parser/types/test_lists.py
--- a/tests/parser/types/test_lists.py
+++ b/tests/parser/types/test_lists.py
@@ -1,3 +1,8 @@
+from vyper.exceptions import (
+    ArrayIndexException,
+)
+
+
 def test_list_tester_code(get_contract_with_gas_estimation):
     list_tester_code = """
 z: int128[3]
@@ -199,3 +204,53 @@ def test_multi4() -> uint256[2][2][2][2]:
         [[[1, 0], [0, 4]], [[0, 0], [0, 0]]],
         [[[444, 0], [0, 0]], [[1, 0], [0, 222]]],
     ]
+
+
+def test_uint256_accessor(get_contract_with_gas_estimation, assert_tx_failed):
+    code = """
+@public
+def bounds_check_uint256(ix: uint256) -> uint256:
+    xs: uint256[3] = [1,2,3]
+    return xs[ix]
+    """
+    c = get_contract_with_gas_estimation(code)
+    assert c.bounds_check_uint256(0) == 1
+    assert c.bounds_check_uint256(2) == 3
+    assert_tx_failed(lambda: c.bounds_check_uint256(3))
+
+
+def test_int128_accessor(get_contract_with_gas_estimation, assert_tx_failed):
+    code = """
+@public
+def bounds_check_int128(ix: int128) -> uint256:
+    xs: uint256[3] = [1,2,3]
+    return xs[ix]
+    """
+    c = get_contract_with_gas_estimation(code)
+    assert c.bounds_check_int128(0) == 1
+    assert c.bounds_check_int128(2) == 3
+    assert_tx_failed(lambda: c.bounds_check_int128(3))
+    assert_tx_failed(lambda: c.bounds_check_int128(-1))
+
+
+def test_compile_time_bounds_check(get_contract_with_gas_estimation, assert_compile_failed):
+    code = """
+@public
+def fail() -> uint256:
+    xs: uint256[3] = [1,2,3]
+    return xs[3]
+    """
+    assert_compile_failed(
+            lambda: get_contract_with_gas_estimation(code),
+            ArrayIndexException
+            )
+    code = """
+@public
+def fail() -> uint256:
+    xs: uint256[3] = [1,2,3]
+    return xs[-1]
+    """
+    assert_compile_failed(
+            lambda: get_contract_with_gas_estimation(code),
+            ArrayIndexException
+            )