Repository: vyperlang/vyper

Issue:
`CompilerPanic` if `as_wei_value` is passed a numeric type other than `uint256`, `uint8`, `int128`, `decimal`
### Version Information

* vyper Version (output of `vyper --version`): 0.3.8+commit.d76c6ed2
* OS: OSX
* Python Version (output of `python --version`): 3.8.0

### What's your issue about?

Although `as_wei_value` accepts any numeric types, its `build_IR` function only defines logic for `uint256`, `uint8`, `int128` and `decimal` leading the compiler to panic for other numeric types.

For example, compiling the following contract leads to the given output.
```Vyper
@external
def test()->uint256:
    fd:int16 = 1
    x:uint256 = as_wei_value(fd, "kether")
    return x
```
```Bash
Traceback (most recent call last):
  File "tester/tester.py", line 28, in <module>
    do()
  File "tester/tester.py", line 23, in do
    abi_a, bytecode_a,_ = compile(code)
  File "tester/tester.py", line 8, in compile
    res = vyper.compile_code(
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/__init__.py", line 192, in compile_code
    return compile_codes(
  File "/Users/trocher/Documents/thesis/vyper/vyper/evm/opcodes.py", line 226, in _wrapper
    return fn(*args, **kwargs)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/__init__.py", line 143, in compile_codes
    raise exc
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/__init__.py", line 138, in compile_codes
    out[contract_name][output_format] = OUTPUT_FORMATS[output_format](compiler_data)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/output.py", line 248, in build_bytecode_output
    return f"0x{compiler_data.bytecode.hex()}"
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/functools.py", line 966, in __get__
    val = self.func(instance)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 150, in bytecode
    self.assembly, is_runtime=False, no_bytecode_metadata=self.no_bytecode_metadata
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/functools.py", line 966, in __get__
    val = self.func(instance)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 141, in assembly
    return generate_assembly(self.ir_nodes, self.no_optimize)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 126, in ir_nodes
    ir, ir_runtime, sigs = self._ir_output
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/functools.py", line 966, in __get__
    val = self.func(instance)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 122, in _ir_output
    return generate_ir_nodes(self.global_ctx, self.no_optimize)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 258, in generate_ir_nodes
    ir_nodes, ir_runtime, function_sigs = module.generate_ir_for_module(global_ctx)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/module.py", line 162, in generate_ir_for_module
    runtime, internal_functions = _runtime_ir(runtime_functions, all_sigs, global_ctx)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/module.py", line 103, in _runtime_ir
    func_ir = generate_ir_for_function(func_ast, all_sigs, global_ctx, skip_nonpayable_check)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/function_definitions/common.py", line 62, in generate_ir_for_function
    o = generate_ir_for_external_function(code, sig, context, skip_nonpayable_check)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/function_definitions/external_function.py", line 199, in generate_ir_for_external_function
    body += [parse_body(code.body, context, ensure_terminated=True)]
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/stmt.py", line 414, in parse_body
    ir = parse_stmt(stmt, context)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/stmt.py", line 388, in parse_stmt
    return Stmt(stmt, context).ir_node
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/stmt.py", line 40, in __init__
    self.ir_node = fn()
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/stmt.py", line 67, in parse_AnnAssign
    rhs = Expr(self.stmt.value, self.context).ir_node
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/expr.py", line 77, in __init__
    self.ir_node = fn()
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/expr.py", line 632, in parse_Call
    return DISPATCH_TABLE[function_name].build_IR(self.expr, self.context)
  File "/Users/trocher/Documents/thesis/vyper/vyper/builtins/_signatures.py", line 72, in decorator_fn
    return wrapped_fn(self, node, subs, kwsubs, context)
  File "/Users/trocher/Documents/thesis/vyper/vyper/builtins/functions.py", line 1075, in build_IR
    raise CompilerPanic(f"Unexpected type: {value.typ}")
vyper.exceptions.CompilerPanic: Unexpected type: uint16

This is an unhandled internal compiler error. Please create an issue on Github to notify the developers.
https://github.com/vyperlang/vyper/issues/new?template=bug.md
```
### How can it be fixed?

Extending the `buildIR` function for all numeric types should fix the issue.

Discussion:

Patch:
diff --git a/docs/built-in-functions.rst b/docs/built-in-functions.rst
index 796e7768..a8cbdfea 100644
--- a/docs/built-in-functions.rst
+++ b/docs/built-in-functions.rst
@@ -914,7 +914,7 @@ Utilities
 
     Take an amount of ether currency specified by a number and a unit and return the integer quantity of wei equivalent to that amount.
 
-    * ``_value``: Value for the ether unit. Any numeric type may be used, however the value cannot be negative.
+    * ``_value``: Value for the ether unit. Any numeric type may be used, however, the value cannot be negative.
     * ``unit``: Ether unit name (e.g. ``"wei"``, ``"ether"``, ``"gwei"``, etc.) indicating the denomination of ``_value``. Must be given as a literal string.
 
     .. code-block:: vyper
@@ -929,6 +929,10 @@ Utilities
         >>> ExampleContract.foo(1)
         1337000000000000000
 
+.. note::
+    When ``as_wei_value`` is given some ``decimal``, the result might be rounded down to the nearest integer, for example, the following is true: ``as_wei_value(12.2, "wei") == 12``.
+
+
 .. py:function:: blockhash(block_num: uint256) -> bytes32
 
     Return the hash of the block at the specified height.
diff --git a/vyper/builtins/functions.py b/vyper/builtins/functions.py
index d9f8f48e..0bd1b1fd 100644
--- a/vyper/builtins/functions.py
+++ b/vyper/builtins/functions.py
@@ -77,20 +77,14 @@ from vyper.semantics.types import (
     TupleT,
 )
 from vyper.semantics.types.bytestrings import _BytestringT
-from vyper.semantics.types.shortcuts import (
-    BYTES4_T,
-    BYTES32_T,
-    INT128_T,
-    INT256_T,
-    UINT8_T,
-    UINT256_T,
-)
+from vyper.semantics.types.shortcuts import BYTES4_T, BYTES32_T, INT256_T, UINT8_T, UINT256_T
 from vyper.semantics.types.utils import type_from_annotation
 from vyper.utils import (
     DECIMAL_DIVISOR,
     EIP_170_LIMIT,
     SHA3_PER_WORD,
     MemoryPositions,
+    SizeLimits,
     bytes_to_int,
     ceil32,
     fourbytes_to_int,
@@ -969,28 +963,34 @@ class AsWeiValue(BuiltinFunctionT):
 
         denom_divisor = self.get_denomination(expr)
         with value.cache_when_complex("value") as (b1, value):
-            if value.typ in (UINT256_T, UINT8_T):
-                sub = [
-                    "with",
-                    "ans",
-                    ["mul", value, denom_divisor],
-                    [
-                        "seq",
-                        [
-                            "assert",
-                            ["or", ["eq", ["div", "ans", value], denom_divisor], ["iszero", value]],
-                        ],
-                        "ans",
-                    ],
-                ]
-            elif value.typ == INT128_T:
-                # signed types do not require bounds checks because the
-                # largest possible converted value will not overflow 2**256
-                sub = ["seq", ["assert", ["sgt", value, -1]], ["mul", value, denom_divisor]]
+            if value.typ in IntegerT.unsigneds():
+                product = IRnode.from_list(["mul", value, denom_divisor])
+                with product.cache_when_complex("ans") as (b2, product):
+                    irlist = ["seq"]
+                    ok = ["or", ["eq", ["div", product, value], denom_divisor], ["iszero", value]]
+                    irlist.append(["assert", ok])
+                    irlist.append(product)
+                    sub = b2.resolve(irlist)
+            elif value.typ in IntegerT.signeds():
+                product = IRnode.from_list(["mul", value, denom_divisor])
+                with product.cache_when_complex("ans") as (b2, product):
+                    irlist = ["seq"]
+                    positive = ["sge", value, 0]
+                    safemul = [
+                        "or",
+                        ["eq", ["div", product, value], denom_divisor],
+                        ["iszero", value],
+                    ]
+                    ok = ["and", positive, safemul]
+                    irlist.append(["assert", ok])
+                    irlist.append(product)
+                    sub = b2.resolve(irlist)
             elif value.typ == DecimalT():
+                # sanity check (so we don't have to use safemul)
+                assert (SizeLimits.MAXDECIMAL * denom_divisor) < 2**256 - 1
                 sub = [
                     "seq",
-                    ["assert", ["sgt", value, -1]],
+                    ["assert", ["sge", value, 0]],
                     ["div", ["mul", value, denom_divisor], DECIMAL_DIVISOR],
                 ]
             else:


Test patch:
diff --git a/tests/functional/builtins/codegen/test_as_wei_value.py b/tests/functional/builtins/codegen/test_as_wei_value.py
index d63f5a8a..21138eda 100644
--- a/tests/functional/builtins/codegen/test_as_wei_value.py
+++ b/tests/functional/builtins/codegen/test_as_wei_value.py
@@ -2,6 +2,8 @@ import pytest
 from eth_utils import to_wei
 
 from tests.utils import decimal_to_int
+from vyper.compiler import compile_code
+from vyper.exceptions import InvalidLiteral, OverflowException
 from vyper.semantics.types import DecimalT
 from vyper.utils import quantize, round_towards_zero
 
@@ -130,3 +132,59 @@ def bar() -> uint8:
     """
     c = get_contract(code)
     assert c.foo() == to_wei(7, "ether")
+
+
+fail_list = [
+    (
+        """
+# Test for negative argument
+@external
+def foo():
+    x: uint256 = as_wei_value(-3, "szabo")
+    """,
+        InvalidLiteral,
+    ),
+    (
+        """
+# Test for 256 bits overflows
+@external
+def foo():
+    x: uint256 = as_wei_value(max_value(uint248), "ether")
+    """,
+        OverflowException,
+    ),
+]
+
+
+@pytest.mark.parametrize("bad_code,exception", fail_list)
+def test_bad_as_wei_code(get_contract, assert_compile_failed, bad_code, exception):
+    with pytest.raises(exception):
+        compile_code(bad_code)
+
+
+valid_list = [
+    """
+@external
+def foo():
+    a:int24 = 31
+    b:uint136 = 31
+    x: uint256 = as_wei_value(a, "szabo") + as_wei_value(b, "ether")
+    """
+]
+
+
+@pytest.mark.parametrize("good_code", valid_list)
+def test_as_wei_success(good_code):
+    assert compile_code(good_code) is not None
+
+
+def test_as_wei_revert(get_contract, tx_failed):
+    code = """
+@external
+def foo(a: uint248) -> uint256:
+    return as_wei_value(a, "grand")
+    """
+    contract = get_contract(code)
+    bad_value = 2**248 - 1
+    with tx_failed():
+        contract.foo(bad_value)