Repository: vyperlang/vyper

Issue:
Large exponential can cause very long Vyper runtime
### Version Information

* vyper Version (output of `vyper --version`): 0.1.0b13+commit.da6d2a3
* OS: osx
* Python Version (output of `python --version`): Python 3.7.4
* Environment (output of `pip freeze`): 
apipkg==1.5
asttokens==1.1.13
atomicwrites==1.3.0
attrdict==2.0.1
attrs==19.3.0
base58==1.0.3
certifi==2019.9.11
cffi==1.13.1
chardet==3.0.4
coverage==4.5.4
coveralls==1.6.0
cryptography==2.8
cytoolz==0.10.0
docopt==0.6.2
entrypoints==0.3
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.3.1
eth-rlp==0.1.2
eth-tester==0.1.0b39
eth-typing==2.1.0
eth-utils==1.7.0
ethpm==0.1.4a19
execnet==1.7.1
filelock==3.0.12
flake8==3.7.8
flake8-bugbear==18.8.0
hexbytes==0.2.0
hypothesis==4.11.7
idna==2.8
importlib-metadata==0.23
ipfshttpclient==0.4.12
isort==4.3.21
jsonschema==2.6.0
lru-dict==1.1.6
mccabe==0.6.1
more-itertools==7.2.0
multiaddr==0.0.8
mypy==0.701
mypy-extensions==0.4.3
netaddr==0.7.19
packaging==19.2
parsimonious==0.8.1
pluggy==0.13.0
protobuf==3.10.0
py==1.8.0
py-ecc==1.7.1
py-evm==0.2.0a42
pycodestyle==2.5.0
pycparser==2.19
pycryptodome==3.9.0
pyethash==0.1.27
pyflakes==2.1.1
pyparsing==2.4.2
pytest==5.2.1
pytest-cov==2.4.0
pytest-xdist==1.18.1
PyYAML==5.1.2
requests==2.22.0
rlp==1.1.0
semantic-version==2.8.2
six==1.12.0
toml==0.10.0
toolz==0.10.0
tox==3.14.0
trie==1.4.0
typed-ast==1.3.5
urllib3==1.25.6
varint==1.0.2
virtualenv==16.7.7
vyper==0.1.0b13
wcwidth==0.1.7
web3==5.0.0b2
websockets==7.0
zipp==0.6.0

### What's your issue about?

This contract:
```
@public
def f0():
    lv0: uint256 = MAX_INT128 ** MAX_INT128
```

should immediately complain the value is out of bounds, which it does for smaller values (e.g., 2**256); instead, it runs for hours .

Another TSTL-produced contract (https://github.com/agroce/tstl/tree/master/examples/vyper)

### How can it be fixed?

Fill this in if you know how to fix it.



Discussion:
:thinking: is this a bug? Or is the vyper compiler correct in trying to calculate a very very large number.
:thinking: safemath on compiler constant folding?
Yeah, not sure if a bug.  Some compilers obviously catch a crazy constant folding request and terminate, but it's certainly not always done.
Since some endusers of the compiler are using it on publicly-exposed infrastructure (e.g. etherscan) I think we should at least bound this and fail to prevent DDoS vectors for them
If defense against DDoS is a real concern, it might be more practical to have some type of reasonable timeout baked in; I suspect it's going to be hard to have a compiler that doesn't have exposed quadratic+ cases that could be exploited.
I don't think infrastructure providers need us to prevent DoS for them, since that is their job and they are presumably much better at it than we are, and some users may have some arcane use case for which they actually do want a long compile time (e.g. embedded execution or some really complicated code).
fair, I would prefer DoS-resistance but I suppose it's not actually a hard requirement for the compiler, as they will have additional protections through containers
we can fix this by adding a sanity check that `right` is not too large here: https://github.com/vyperlang/vyper/blob/aac0be8db89981c4ae27bc606fc411c531c44a80/vyper/ast/nodes.py#L1000-L1004

Resolving patch:
diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py
--- a/vyper/ast/nodes.py
+++ b/vyper/ast/nodes.py
@@ -3,6 +3,7 @@
 import copy
 import decimal
 import functools
+import math
 import operator
 import pickle
 import sys
@@ -1080,6 +1081,15 @@ def _op(self, left, right):
             raise TypeMismatch("Cannot perform exponentiation on decimal values.", self._parent)
         if right < 0:
             raise InvalidOperation("Cannot calculate a negative power", self._parent)
+        # prevent a compiler hang. we are ok with false positives at this
+        # stage since we are just trying to filter out inputs which can cause
+        # the compiler to hang. the others will get caught during constant
+        # folding or codegen.
+        # l**r > 2**256
+        # r * ln(l) > ln(2 ** 256)
+        # r > ln(2 ** 256) / ln(l)
+        if right > math.log(decimal.Decimal(2**257)) / math.log(decimal.Decimal(left)):
+            raise InvalidLiteral("Out of bounds", self)
         return int(left**right)
 
 


Test patch:
diff --git a/tests/functional/codegen/types/numbers/test_exponents.py b/tests/functional/codegen/types/numbers/test_exponents.py
--- a/tests/functional/codegen/types/numbers/test_exponents.py
+++ b/tests/functional/codegen/types/numbers/test_exponents.py
@@ -3,6 +3,18 @@
 from hypothesis import strategies as st
 
 from vyper.codegen.arithmetic import calculate_largest_base, calculate_largest_power
+from vyper.compiler import compile_code
+from vyper.exceptions import InvalidLiteral
+
+
+def test_compiler_hang():
+    code = """
+@external
+def f0():
+    lv0: uint256 = max_value(int128) ** max_value(int128)
+    """
+    with pytest.raises(InvalidLiteral):
+        compile_code(code)
 
 
 @pytest.mark.fuzzing