Repository: vyperlang/vyper

Issue:
Exponent with units is not correct
### What's your issue about?

The following program is rejected by the compiler:
```
@public
def foo():
    a : num(wei)
    b : num(wei**2)
    a = 2
    b = a**2
```


### How can it be fixed?

In arithmetic pow, it calls `combine_units` which only handles multiplication and division, in my point of view.

#### Cute Animal Picture

![image](https://user-images.githubusercontent.com/5641590/33633497-b22bffb0-d9d6-11e7-894e-c4fba7b3acb4.png)




Discussion:
to reword, you think `num(wei**2)` is causing the issue?
Look at https://github.com/ethereum/viper/blob/b0cec59d24051e3bb3577475211c277e78c68a23/viper/parser/expr.py#L290. First, exponent should be a pure value instead of unit. You already fixed that. Second, `a**b` should *multiply* the unit of `a` b times. However, I think `combine_units` only *add* or *subtract* the exponent of the unit instead of doing multiplication. Back to the example, the compiler thinks the unit of a**2 is wei instead of wei**2, and that's why the program is rejected. Basically, you did it correctly when paring unit https://github.com/ethereum/viper/blob/b0cec59d24051e3bb3577475211c277e78c68a23/viper/types.py#L237, but not correctly when you perform unit arithmetic(pow).
By the way, I think it is hard to check the unit type of the following program:
```
def foo(a:num):
    b:num(wei)
    c = b ** a
```
It is not possible to determine the unit of `c` at compile time ( it is `wei`**a). It is better to reject this program.
> By the way, I think it is hard to check the unit type of the following program:
>```python
>def foo(a:num):
>     b:num(wei)
>     c = b ** a
>```
>It is not possible to determine the unit of c at compile time ( it is wei**a). It is better to reject this program.

I agree with this
@yzhang90 Could you explain why it's not possible to determine `c` at compile time (to me it should be `num(wei)`)? 
Depends on your interpretation, but `wei**2` is different than `wei`, so having `wei**n` is the not detectable at compile time.

I think we can discuss this further whether to allow. I'm an engineer, so I have a strong aversion to mixing units lol
@DavidKnott  As what @fubuloubu already mentioned, 
```
   a: num(sec)
   b = a * a
```
The unit of `b` should be  sec^2.  `b=a**2` should have the same unit.
Consider the following program:
```
def foo(a:num):
    b:num(sec)
    c:num(sec**2)
    c = b ** a
```
If the argument `a` is 2, then the last assignment is correct, Otherwise, it is not.

Resolving patch:
diff --git a/viper/parser/expr.py b/viper/parser/expr.py
--- a/viper/parser/expr.py
+++ b/viper/parser/expr.py
@@ -287,8 +287,12 @@ def arithmetic(self):
                 raise TypeMismatchException("Cannot use positional values as exponential arguments!", self.expr)
             if right.typ.unit:
                 raise TypeMismatchException("Cannot use unit values as exponents", self.expr)
-            new_unit = combine_units(left.typ.unit, right.typ.unit)
+            if  ltyp != 'num' and isinstance(self.expr.right, ast.Name):
+                raise TypeMismatchException("Cannot use dynamic values as exponents, for unit base types", self.expr)
             if ltyp == rtyp == 'num':
+                new_unit = left.typ.unit
+                if left.typ.unit and not isinstance(self.expr.right, ast.Name):
+                    new_unit = {left.typ.unit.copy().popitem()[0]:  self.expr.right.n}
                 o = LLLnode.from_list(['exp', left, right], typ=BaseType('num', new_unit), pos=getpos(self.expr))
             else:
                 raise TypeMismatchException('Only whole number exponents are supported', self.expr)


Test patch:
diff --git a/tests/parser/syntax/test_unit_exponents.py b/tests/parser/syntax/test_unit_exponents.py
new file mode 100644
--- /dev/null
+++ b/tests/parser/syntax/test_unit_exponents.py
@@ -0,0 +1,44 @@
+import pytest
+from pytest import raises
+
+from viper import compiler
+from viper.exceptions import TypeMismatchException
+
+fail_list = [
+    """
+@public
+def baa() -> decimal:
+    return 2.0 ** 2
+    """,
+    """
+@public
+def foo(a:num):
+    b:num(sec)
+    c:num(sec**2)
+    c = b ** a
+    """
+]
+
+
+@pytest.mark.parametrize('bad_code', fail_list)
+def test_exponent_fail(bad_code):
+
+    with raises(TypeMismatchException):
+        compiler.compile(bad_code)
+
+
+valid_list = [
+    """
+@public
+def foo():
+    a : num(wei)
+    b : num(wei**2)
+    a = 2
+    b = a**2
+    """,
+]
+
+
+@pytest.mark.parametrize('good_code', valid_list)
+def test_exponent_success(good_code):
+    assert compiler.compile(good_code) is not None