Repository: vyperlang/vyper

Issue:
VIP: Simplify+generalize conversion rules
## Simple Summary
Simplify and generalize type conversion rules.

## Motivation
https://github.com/vyperlang/vyper/issues/1093 introduced type conversion rules between all the types in vyper that existed at the time. Now that the number of numeric types is growing, we can't just add more rules for every type that gets added. This will result in N^2 rules which is hard to code, and hard to reason about.

This VIP is basically a distillation of #1093 into rules that will generalize to more types. It modifies the rules for address, making it harder to convert address into other types (however - it can be converted via intermediate conversion to `bytes32`, which means "I really want to do this").

## Specification
Terminology:
- `bitcast`: leave the input byte-for-byte unchanged
- `padconvert`: convert left- or right-padding to the other one. (integers are left-padded with zeroes; bytestrings are right-padded).
- `top`: leftmost
- `bottom`: rightmost
- `signextend`: fill the top bits with 1's if the sign bit is set (e.g. signextend for int8 will look at the 8th bit from the right)

The general principles are:
- `padconvert` is applied for conversions between left-padded (bytes) and right-padded (other types)
- converting integers to decimals involves truncation
- conversion from a smaller-width to larger-width type is otherwise a no-op
- conversion from a larger-width to smaller-width type requires a clamp
- conversions involving bytes-like types are legal for `Bytes[N<=32]` and `String[N<=32]`
- conversion from a numeric type to a bytes-like type is legal so long as the output type is at-least-as-wide as the input type.
  - For instance, `int128` could be converted to `Bytes[N>=16]`.
- conversion from a bytes-like type to a numeric type requires a clamp.
- conversion between unsigned and signed requires a clamp.
  - For instance, `uint256` to `int128` requires a check that the input is less than `INT128_MAX`.
  - For instance, `int128` to `uint256` requires a check that the input is greater than `0`.

The following (WIP) table summarizes each case:

| from | to | behavior | clamps |
|---|---|---|---|
| `numeric` | `bytesM` | padconvert to `M` bytes | input must have fewer than `M` bytes set |
| `bool` | `bytesM` | padconvert to `M` bytes | input must have fewer than `M` bytes set |
| `address` | `bytesM` | padconvert to `M` bytes | input must have fewer than `M` bytes set |
| `integer` | `bytesM` | padconvert to `M` bytes | input must have fewer than `M` bytes set |
| `bytesN` | `bytesM` | bitcast the input | input must have fewer than `M` bytes set |
| `*` | `bool` | returns `iszero(iszero(input))` | (none) |
| `bool` | `decimal` | `1.0 if True else 0.0` | (none) |
| `bool` | `*` (except decimal) | bitcast the input | (none) |
| `bytesM` | `intN` | padconvert the input with sign extension | input must have fewer than N bits set |
| `decimal` | `uintN` | truncate the input, e.g. `1.0` -> `1` | output must be in bounds for the type, and be >=0 |
| `decimal` | `intN` | truncate the input, e.g. `1.0` -> `1` | output must be in bounds for the type |
| `address` | `uintN` | bitcast the input | input must have fewer than N bits set. signed integers not allowed |
| `bytesM` | `address` | padconvert the input | input must have fewer than 20 bytes set |
| `uintN` | `address` | bitcast the input | input must have fewer than 160 bits set. signed integers not allowed |
| `bytesM` | `decimal` | padconvert the input | input must have fewer than 168 bits set |
| `intN` | `decimal` | promote the input, e.g. `1` -> `1.0` | output must be in bounds |
| `uintN` | `decimal` | promote the input, e.g. `1` -> `1.0` | output must be in bounds, and be <decimal::max_value |
| `udecimal` (hypothetical) | `uintN` | truncate the input | output must be in bounds for the type, and be >=0 |
| `udecimal` (hypothetical) | `intN` | truncate the input | output must be in bounds for the type, and be <intN::max_value |


## Backwards Compatibility
Should stay backwards compatible EXCEPT for address and possibly decimal conversions. `address` is no longer convertible with as many types, and `decimal` now has bitcasting semantics instead of truncation when converting with bytes types.

## Dependencies

## References
https://github.com/vyperlang/vyper/issues/967
https://github.com/vyperlang/vyper/issues/1093
https://github.com/vyperlang/vyper/issues/1901
https://github.com/vyperlang/vyper/issues/2317

## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/)


Discussion:
lgtm-com[bot]: This pull request **introduces 3 alerts** when merging 50af15e9ceb39c47ad613ef4b1f812b3ec1e5169 into aa8affbc4ada843ea53d6e8f5a1c2d0726d0137b - [view on LGTM.com](https://lgtm.com/projects/g/vyperlang/vyper/rev/pr-769a03e1de703bafaf491e12da4c71132119c321)

**new alerts:**

* 2 for Unused import
* 1 for Syntax error
lgtm-com[bot]: This pull request **introduces 11 alerts** when merging 7e732197639b94d025d259f10a5d2b65f0c42929 into aa8affbc4ada843ea53d6e8f5a1c2d0726d0137b - [view on LGTM.com](https://lgtm.com/projects/g/vyperlang/vyper/rev/pr-565be8906f5a32f3aa2c0b5f070c2572b308db46)

**new alerts:**

* 7 for Unused import
* 4 for Unused local variable
lgtm-com[bot]: This pull request **introduces 2 alerts** when merging a9f8d9985718ee4c0a9bf302f591a4c35925600f into aa8affbc4ada843ea53d6e8f5a1c2d0726d0137b - [view on LGTM.com](https://lgtm.com/projects/g/vyperlang/vyper/rev/pr-79b3fc88e6dd92280e373a1cfe72c2c9997ea624)

**new alerts:**

* 1 for Conflicting attributes in base classes
* 1 for Wrong number of arguments in a call
lgtm-com[bot]: This pull request **introduces 4 alerts** when merging 238949a400abaeb44f64951d4f9a649fdc540a05 into aa8affbc4ada843ea53d6e8f5a1c2d0726d0137b - [view on LGTM.com](https://lgtm.com/projects/g/vyperlang/vyper/rev/pr-c458e92cbff904370a4bade427245fa9ca8b6a0b)

**new alerts:**

* 2 for Unused import
* 1 for Conflicting attributes in base classes
* 1 for Unused local variable
lgtm-com[bot]: This pull request **introduces 2 alerts** when merging e3a729f97f883aa4edab07f13bfc3d27cbdd14cf into aa8affbc4ada843ea53d6e8f5a1c2d0726d0137b - [view on LGTM.com](https://lgtm.com/projects/g/vyperlang/vyper/rev/pr-f5b2e4977dd8a32f46615327526b1a1ee7bb20a1)

**new alerts:**

* 1 for Conflicting attributes in base classes
* 1 for Unused local variable
lgtm-com[bot]: This pull request **introduces 2 alerts** when merging fd6f3332f126f0ea844f8455c3619b6e17483218 into aa8affbc4ada843ea53d6e8f5a1c2d0726d0137b - [view on LGTM.com](https://lgtm.com/projects/g/vyperlang/vyper/rev/pr-bae2794ea9e6d30b2693e7cd6ba884a5dd4ccac1)

**new alerts:**

* 1 for Conflicting attributes in base classes
* 1 for Unused local variable
lgtm-com[bot]: This pull request **introduces 2 alerts** when merging 5eb59d37f12eea7175cc7d42c378559e20fb9f43 into aa8affbc4ada843ea53d6e8f5a1c2d0726d0137b - [view on LGTM.com](https://lgtm.com/projects/g/vyperlang/vyper/rev/pr-f43b77487b0f40844ed16f796c962994135eaa70)

**new alerts:**

* 1 for Conflicting attributes in base classes
* 1 for Unused local variable
codecov-commenter: # [Codecov](https://codecov.io/gh/vyperlang/vyper/pull/2694?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) Report
> Merging [#2694](https://codecov.io/gh/vyperlang/vyper/pull/2694?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) (533eecc) into [master](https://codecov.io/gh/vyperlang/vyper/commit/aa8affbc4ada843ea53d6e8f5a1c2d0726d0137b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) (aa8affb) will **decrease** coverage by `33.42%`.
> The diff coverage is `41.55%`.

> :exclamation: Current head 533eecc differs from pull request most recent head d4f1797. Consider uploading reports for the commit d4f1797 to get more accurate results

[![Impacted file tree graph](https://codecov.io/gh/vyperlang/vyper/pull/2694/graphs/tree.svg?width=650&height=150&src=pr&token=fTg5Co13HV&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang)](https://codecov.io/gh/vyperlang/vyper/pull/2694?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang)

```diff
@@             Coverage Diff             @@
##           master    #2694       +/-   ##
===========================================
- Coverage   86.43%   53.00%   -33.43%     
===========================================
  Files          91       91               
  Lines        9883     9837       -46     
  Branches     2492     2426       -66     
===========================================
- Hits         8542     5214     -3328     
- Misses        839     4021     +3182     
- Partials      502      602      +100     
```


| [Impacted Files](https://codecov.io/gh/vyperlang/vyper/pull/2694?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) | Coverage Δ | |
|---|---|---|
| [vyper/builtin\_functions/signatures.py](https://codecov.io/gh/vyperlang/vyper/pull/2694/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvYnVpbHRpbl9mdW5jdGlvbnMvc2lnbmF0dXJlcy5weQ==) | `54.34% <ø> (-13.16%)` | :arrow_down: |
| [vyper/builtin\_functions/convert.py](https://codecov.io/gh/vyperlang/vyper/pull/2694/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvYnVpbHRpbl9mdW5jdGlvbnMvY29udmVydC5weQ==) | `26.12% <24.65%> (-49.88%)` | :arrow_down: |
| [vyper/utils.py](https://codecov.io/gh/vyperlang/vyper/pull/2694/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvdXRpbHMucHk=) | `60.13% <33.33%> (-25.59%)` | :arrow_down: |
| [vyper/codegen/core.py](https://codecov.io/gh/vyperlang/vyper/pull/2694/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvY29kZWdlbi9jb3JlLnB5) | `48.94% <68.18%> (-35.45%)` | :arrow_down: |
| [vyper/codegen/types/types.py](https://codecov.io/gh/vyperlang/vyper/pull/2694/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvY29kZWdlbi90eXBlcy90eXBlcy5weQ==) | `69.17% <95.12%> (-15.75%)` | :arrow_down: |
| [vyper/builtin\_functions/functions.py](https://codecov.io/gh/vyperlang/vyper/pull/2694/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvYnVpbHRpbl9mdW5jdGlvbnMvZnVuY3Rpb25zLnB5) | `54.83% <100.00%> (-33.54%)` | :arrow_down: |
| [vyper/codegen/expr.py](https://codecov.io/gh/vyperlang/vyper/pull/2694/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvY29kZWdlbi9leHByLnB5) | `38.66% <100.00%> (-40.84%)` | :arrow_down: |
| [vyper/lll/optimizer.py](https://codecov.io/gh/vyperlang/vyper/pull/2694/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvbGxsL29wdGltaXplci5weQ==) | `62.56% <100.00%> (-23.72%)` | :arrow_down: |
| [vyper/semantics/types/abstract.py](https://codecov.io/gh/vyperlang/vyper/pull/2694/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvc2VtYW50aWNzL3R5cGVzL2Fic3RyYWN0LnB5) | `94.28% <100.00%> (-5.72%)` | :arrow_down: |
| [vyper/semantics/types/value/bytes\_fixed.py](https://codecov.io/gh/vyperlang/vyper/pull/2694/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvc2VtYW50aWNzL3R5cGVzL3ZhbHVlL2J5dGVzX2ZpeGVkLnB5) | `80.76% <100.00%> (-19.24%)` | :arrow_down: |
| ... and [69 more](https://codecov.io/gh/vyperlang/vyper/pull/2694/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/vyperlang/vyper/pull/2694?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/vyperlang/vyper/pull/2694?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang). Last update [aa8affb...d4f1797](https://codecov.io/gh/vyperlang/vyper/pull/2694?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang).

lgtm-com[bot]: This pull request **introduces 2 alerts** when merging d4f1797aba44742395a9975091ed945154373991 into cfe26e1b45d02107acfa305c2f750565a7970bd1 - [view on LGTM.com](https://lgtm.com/projects/g/vyperlang/vyper/rev/pr-d089a995ccaa282aee23a6d9769a32fd2558b31a)

**new alerts:**

* 1 for Conflicting attributes in base classes
* 1 for Unused local variable
lgtm-com[bot]: This pull request **introduces 2 alerts** when merging c916f580a7fb9498ad5561dfd98220b83d0e6b18 into cfe26e1b45d02107acfa305c2f750565a7970bd1 - [view on LGTM.com](https://lgtm.com/projects/g/vyperlang/vyper/rev/pr-56f5b1c96a74f65d8ce6ce3673726e2ee697a9a6)

**new alerts:**

* 1 for Conflicting attributes in base classes
* 1 for Unused local variable
lgtm-com[bot]: This pull request **introduces 2 alerts** when merging 4ed8435f5f469a9695bd655c5a6b16a9352413db into cfe26e1b45d02107acfa305c2f750565a7970bd1 - [view on LGTM.com](https://lgtm.com/projects/g/vyperlang/vyper/rev/pr-2136ef3ed1bb24c5194cae231070c9a3908db8c6)

**new alerts:**

* 1 for Conflicting attributes in base classes
* 1 for Unused local variable
lgtm-com[bot]: This pull request **introduces 1 alert** when merging 6838dd055af416158dad523e102d24030a37dd7b into cfe26e1b45d02107acfa305c2f750565a7970bd1 - [view on LGTM.com](https://lgtm.com/projects/g/vyperlang/vyper/rev/pr-34d76d0d5d0ecdbf7313d64c645137b870da1b4e)

**new alerts:**

* 1 for Unused local variable

Patch:
diff --git a/vyper/builtin_functions/convert.py b/vyper/builtin_functions/convert.py
index c7a09104..8dec4d27 100644
--- a/vyper/builtin_functions/convert.py
+++ b/vyper/builtin_functions/convert.py
@@ -1,528 +1,412 @@
+import decimal
+import functools
 import math
-import warnings
-from decimal import Decimal
 
 from vyper import ast as vy_ast
-from vyper.builtin_functions.signatures import signature
 from vyper.codegen.core import (
     LLLnode,
-    add_ofst,
+    bytes_clamp,
+    bytes_data_ptr,
     clamp_basetype,
     get_bytearray_length,
     getpos,
     int_clamp,
-    load_op,
+    load_word,
+    sar,
     shl,
     shr,
-    wordsize,
+    unwrap_location,
 )
+from vyper.codegen.expr import Expr
 from vyper.codegen.types import (
-    DYNAMIC_ARRAY_OVERHEAD,
     BaseType,
+    ByteArrayLike,
     ByteArrayType,
     StringType,
-    get_type,
+    is_base_type,
+    is_bytes_m_type,
+    is_decimal_type,
+    is_integer_type,
 )
-from vyper.evm.opcodes import version_check
-from vyper.exceptions import InvalidLiteral, StructureException, TypeMismatch
-from vyper.utils import DECIMAL_DIVISOR, MemoryPositions, SizeLimits
-
-
-def byte_array_to_num(arg, out_type):
-    """
-    Takes a <32 byte array as input, and outputs a number.
-    """
-    # the location of the bytestring
-    bs_start = (
-        LLLnode.from_list("bs_start", typ=arg.typ, location=arg.location, encoding=arg.encoding)
-        if arg.is_complex_lll
-        else arg
-    )
-
-    if arg.location == "storage":
-        len_ = get_bytearray_length(bs_start)
-        data = LLLnode.from_list(["sload", add_ofst(bs_start, 1)], typ=BaseType("int256"))
-    else:
-        op = load_op(arg.location)
-        len_ = LLLnode.from_list([op, bs_start], typ=BaseType("int256"))
-        data = LLLnode.from_list([op, add_ofst(bs_start, 32)], typ=BaseType("int256"))
-
+from vyper.exceptions import CompilerPanic, InvalidLiteral, StructureException, TypeMismatch
+from vyper.utils import DECIMAL_DIVISOR, SizeLimits, int_bounds
+
+
+def _FAIL(ityp, otyp, pos=None):
+    raise TypeMismatch(f"Can't convert {ityp} to {otyp}", pos)
+
+
+# helper function for `_input_types`
+# generates a string representation of a type
+# (makes up for lack of proper hierarchy in LLL type system)
+# (ideally would use type generated during annotation, but
+# not available for builtins)
+def _type_class_of(typ):
+    if is_integer_type(typ):
+        return "int"
+    if is_bytes_m_type(typ):
+        return "bytes_m"
+    if is_decimal_type(typ):
+        return "decimal"
+    if isinstance(typ, BaseType):
+        return typ.typ  # e.g., "bool"
+    if isinstance(typ, ByteArrayType):
+        return "bytes"
+    if isinstance(typ, StringType):
+        return "string"
+
+
+def _input_types(*allowed_types):
+    def decorator(f):
+        @functools.wraps(f)
+        def check_input_type(expr, arg, out_typ):
+            # convert arg to out_typ.
+            # (expr is the AST corresponding to `arg`)
+            ityp = _type_class_of(arg.typ)
+            ok = ityp in allowed_types
+            # user safety: disallow convert from type to itself
+            ok &= arg.typ != out_typ
+            if not ok:
+                _FAIL(arg.typ, out_typ, expr)
+            return f(expr, arg, out_typ)
+
+        return check_input_type
+
+    return decorator
+
+
+def _bytes_to_num(arg, out_typ, signed):
     # converting a bytestring to a number:
-    # bytestring is right-padded with zeroes, int is left-padded.
-    # convert by shr the number of zero bytes (converted to bits)
+    # bytestring and bytes_m are right-padded with zeroes, int is left-padded.
+    # convert by shr or sar the number of zero bytes (converted to bits)
     # e.g. "abcd000000000000" -> bitcast(000000000000abcd, output_type)
-    num_zero_bits = ["mul", 8, ["sub", 32, "len_"]]
-    bitcasted = LLLnode.from_list(shr(num_zero_bits, "val"), typ=out_type)
-
-    result = clamp_basetype(bitcasted)
-
-    # TODO use cache_when_complex for these `with` values
-    ret = ["with", "val", data, ["with", "len_", len_, result]]
-    if arg.is_complex_lll:
-        ret = ["with", "bs_start", arg, ret]
-    return LLLnode.from_list(
-        ret,
-        typ=BaseType(out_type),
-        annotation=f"__intrinsic__byte_array_to_num({out_type})",
-    )
-
-
-@signature(("decimal", "int128", "int256", "uint8", "uint256", "address", "bytes32", "Bytes"), "*")
-def to_bool(expr, args, kwargs, context):
-    in_arg = args[0]
-    input_type, _ = get_type(in_arg)
-
-    if input_type == "Bytes":
-        if in_arg.typ.maxlen > 32:
-            raise TypeMismatch(
-                f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to bool",
-                expr,
-            )
-        else:
-            num = byte_array_to_num(in_arg, "uint256")
-            return LLLnode.from_list(
-                ["iszero", ["iszero", num]], typ=BaseType("bool"), pos=getpos(expr)
-            )
 
+    if isinstance(arg.typ, ByteArrayLike):
+        _len = get_bytearray_length(arg)
+        arg = load_word(bytes_data_ptr(arg))
+        num_zero_bits = ["mul", 8, ["sub", 32, _len]]
+    elif is_bytes_m_type(arg.typ):
+        info = arg.typ._bytes_info
+        num_zero_bits = 8 * (32 - info.m)
     else:
-        return LLLnode.from_list(
-            ["iszero", ["iszero", in_arg]], typ=BaseType("bool"), pos=getpos(expr)
-        )
-
-
-@signature(("decimal", "int128", "int256", "uint256", "bytes32", "Bytes"), "*")
-def to_uint8(expr, args, kwargs, context):
-    in_arg = args[0]
-    input_type, _ = get_type(in_arg)
-
-    if input_type == "Bytes":
-        if in_arg.typ.maxlen > 32:
-            raise TypeMismatch(
-                f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to uint8",
-                expr,
-            )
-        else:
-            # uint8 clamp is already applied in byte_array_to_num
-            in_arg = byte_array_to_num(in_arg, "uint8")
+        raise CompilerPanic("unreachable")  # pragma: notest
 
+    if signed:
+        ret = sar(num_zero_bits, arg)
     else:
-        # cast to output type so clamp_basetype works
-        in_arg = LLLnode.from_list(in_arg, typ="uint8")
-
-    return LLLnode.from_list(clamp_basetype(in_arg), typ=BaseType("uint8"), pos=getpos(expr))
-
-
-@signature(
-    (
-        "num_literal",
-        "bool",
-        "decimal",
-        "uint8",
-        "int256",
-        "uint256",
-        "address",
-        "bytes32",
-        "Bytes",
-        "String",
-    ),
-    "*",
-)
-def to_int128(expr, args, kwargs, context):
-    in_arg = args[0]
-    input_type, _ = get_type(in_arg)
-
-    if input_type == "num_literal":
-        if isinstance(in_arg, int):
-            if not SizeLimits.in_bounds("int128", in_arg):
-                raise InvalidLiteral(f"Number out of range: {in_arg}")
-            return LLLnode.from_list(in_arg, typ=BaseType("int128"), pos=getpos(expr))
-        elif isinstance(in_arg, Decimal):
-            if not SizeLimits.in_bounds("int128", math.trunc(in_arg)):
-                raise InvalidLiteral(f"Number out of range: {math.trunc(in_arg)}")
-            return LLLnode.from_list(math.trunc(in_arg), typ=BaseType("int128"), pos=getpos(expr))
-        else:
-            raise InvalidLiteral(f"Unknown numeric literal type: {in_arg}")
-
-    elif input_type in ("bytes32", "int256"):
-        if in_arg.typ.is_literal:
-            if not SizeLimits.in_bounds("int128", in_arg.value):
-                raise InvalidLiteral(f"Number out of range: {in_arg.value}", expr)
-            else:
-                return LLLnode.from_list(in_arg, typ=BaseType("int128"), pos=getpos(expr))
-        else:
-            # cast to output type so clamp_basetype works
-            in_arg = LLLnode.from_list(in_arg, typ="int128")
-            return LLLnode.from_list(
-                clamp_basetype(in_arg),
-                typ=BaseType("int128"),
-                pos=getpos(expr),
-            )
-
-    # CMC 20211020: what is the purpose of this .. it lops off 32 bits
-    elif input_type == "address":
-        return LLLnode.from_list(
-            ["signextend", 15, ["and", in_arg, (SizeLimits.ADDRSIZE - 1)]],
-            typ=BaseType("int128"),
-            pos=getpos(expr),
-        )
-
-    elif input_type in ("String", "Bytes"):
-        if in_arg.typ.maxlen > 32:
-            raise TypeMismatch(
-                f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to int128",
-                expr,
-            )
-        return byte_array_to_num(in_arg, "int128")
-
-    elif input_type == "uint256":
-        if in_arg.typ.is_literal:
-            if not SizeLimits.in_bounds("int128", in_arg.value):
-                raise InvalidLiteral(f"Number out of range: {in_arg.value}", expr)
-            else:
-                return LLLnode.from_list(in_arg, typ=BaseType("int128"), pos=getpos(expr))
-
-        # !! do not use clamp_basetype. check that 0 <= input <= MAX_INT128.
-        res = int_clamp(in_arg, 127, signed=False)
-        return LLLnode.from_list(
-            res,
-            typ="int128",
-            pos=getpos(expr),
-        )
-
-    elif input_type == "decimal":
-        # cast to int128 so clamp_basetype works
-        res = LLLnode.from_list(["sdiv", in_arg, DECIMAL_DIVISOR], typ="int128")
-        return LLLnode.from_list(clamp_basetype(res), typ="int128", pos=getpos(expr))
-
-    elif input_type in ("bool", "uint8"):
-        # note: for int8, would need signextend
-        return LLLnode.from_list(in_arg, typ=BaseType("int128"), pos=getpos(expr))
+        ret = shr(num_zero_bits, arg)
 
-    else:
-        raise InvalidLiteral(f"Invalid input for int128: {in_arg}", expr)
+    annotation = (f"__intrinsic__byte_array_to_num({out_typ})",)
+    return LLLnode.from_list(ret, annotation=annotation)
 
 
-@signature(
-    ("num_literal", "int128", "int256", "uint8", "bytes32", "Bytes", "address", "bool", "decimal"),
-    "*",
-)
-def to_uint256(expr, args, kwargs, context):
-    in_arg = args[0]
-    input_type, _ = get_type(in_arg)
-
-    if input_type == "num_literal":
-        if isinstance(in_arg, int):
-            if not SizeLimits.in_bounds("uint256", in_arg):
-                raise InvalidLiteral(f"Number out of range: {in_arg}")
-            return LLLnode.from_list(
-                in_arg,
-                typ=BaseType(
-                    "uint256",
-                ),
-                pos=getpos(expr),
-            )
-        elif isinstance(in_arg, Decimal):
-            if not SizeLimits.in_bounds("uint256", math.trunc(in_arg)):
-                raise InvalidLiteral(f"Number out of range: {math.trunc(in_arg)}")
-            return LLLnode.from_list(math.trunc(in_arg), typ=BaseType("uint256"), pos=getpos(expr))
-        else:
-            raise InvalidLiteral(f"Unknown numeric literal type: {in_arg}")
-
-    elif isinstance(in_arg, LLLnode) and input_type in ("int128", "int256"):
-        return LLLnode.from_list(["clampge", in_arg, 0], typ=BaseType("uint256"), pos=getpos(expr))
-
-    elif isinstance(in_arg, LLLnode) and input_type == "decimal":
-        return LLLnode.from_list(
-            ["div", ["clampge", in_arg, 0], DECIMAL_DIVISOR],
-            typ=BaseType("uint256"),
-            pos=getpos(expr),
-        )
-
-    elif isinstance(in_arg, LLLnode) and input_type in ("bool", "uint8"):
-        return LLLnode.from_list(in_arg, typ=BaseType("uint256"), pos=getpos(expr))
-
-    elif isinstance(in_arg, LLLnode) and input_type in ("bytes32", "address"):
-        return LLLnode(
-            value=in_arg.value, args=in_arg.args, typ=BaseType("uint256"), pos=getpos(expr)
-        )
-
-    elif isinstance(in_arg, LLLnode) and input_type == "Bytes":
-        if in_arg.typ.maxlen > 32:
-            raise InvalidLiteral(
-                f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to uint256",
-                expr,
-            )
-        return byte_array_to_num(in_arg, "uint256")
+# truncate from fixed point decimal to int
+def _fixed_to_int(x, out_typ, decimals=10):
+    return LLLnode.from_list(["sdiv", x, 10 ** decimals], typ=out_typ)
 
-    else:
-        raise InvalidLiteral(f"Invalid input for uint256: {in_arg}", expr)
 
+# promote from int to fixed point decimal
+def _int_to_fixed(x, out_typ, decimals=10):
+    return LLLnode.from_list(["mul", x, 10 ** decimals], typ=out_typ)
 
-# TODO address support isn't added yet because of weirdness with int128 -> address
-# conversions. in the next breaking release we should modify how address conversions work
-# so it can make sense for many signed integer types. @iamdefinitelyahuman
-@signature(
-    ("num_literal", "int128", "uint8", "uint256", "bytes32", "Bytes", "String", "bool", "decimal"),
-    "*",
-)
-def to_int256(expr, args, kwargs, context):
-    in_arg = args[0]
-    input_type, _ = get_type(in_arg)
-
-    if input_type == "num_literal":
-        if isinstance(in_arg, int):
-            if not SizeLimits.in_bounds("int256", in_arg):
-                raise InvalidLiteral(f"Number out of range: {in_arg}")
-            return LLLnode.from_list(
-                in_arg,
-                typ=BaseType(
-                    "int256",
-                ),
-                pos=getpos(expr),
-            )
-        elif isinstance(in_arg, Decimal):
-            if not SizeLimits.in_bounds("int256", math.trunc(in_arg)):
-                raise InvalidLiteral(f"Number out of range: {math.trunc(in_arg)}")
-            return LLLnode.from_list(math.trunc(in_arg), typ=BaseType("int256"), pos=getpos(expr))
-        else:
-            raise InvalidLiteral(f"Unknown numeric literal type: {in_arg}")
 
-    elif isinstance(in_arg, LLLnode) and input_type == "int128":
-        return LLLnode.from_list(in_arg, typ=BaseType("int256"), pos=getpos(expr))
+def _check_bytes(expr, arg, output_type, max_bytes_allowed):
+    if isinstance(arg.typ, ByteArrayLike):
+        if arg.typ.maxlen > max_bytes_allowed:
+            _FAIL(arg.typ, output_type, expr)
+    else:
+        # sanity check. should not have conversions to non-base types
+        assert output_type.memory_bytes_required == 32
 
-    elif isinstance(in_arg, LLLnode) and input_type == "uint256":
-        if version_check(begin="constantinople"):
-            upper_bound = ["shl", 255, 1]
-        else:
-            upper_bound = -(2 ** 255)
-        return LLLnode.from_list(
-            ["uclamplt", in_arg, upper_bound], typ=BaseType("int256"), pos=getpos(expr)
-        )
-
-    elif isinstance(in_arg, LLLnode) and input_type == "decimal":
-        return LLLnode.from_list(
-            ["sdiv", in_arg, DECIMAL_DIVISOR],
-            typ=BaseType("int256"),
-            pos=getpos(expr),
-        )
-
-    elif isinstance(in_arg, LLLnode) and input_type in ("bool", "uint8"):
-        return LLLnode.from_list(in_arg, typ=BaseType("int256"), pos=getpos(expr))
-
-    elif isinstance(in_arg, LLLnode) and input_type in ("bytes32", "address"):
-        return LLLnode(
-            value=in_arg.value, args=in_arg.args, typ=BaseType("int256"), pos=getpos(expr)
-        )
-
-    elif isinstance(in_arg, LLLnode) and input_type in ("Bytes", "String"):
-        if in_arg.typ.maxlen > 32:
-            raise TypeMismatch(
-                f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to int256",
-                expr,
-            )
-        return byte_array_to_num(in_arg, "int256")
 
+def _literal_int(expr, out_typ):
+    # TODO: possible to reuse machinery from expr.py?
+    int_info = out_typ._int_info
+    if isinstance(expr, vy_ast.Hex):
+        val = int(expr.value, 16)
+    elif isinstance(expr, vy_ast.Bytes):
+        val = int.from_bytes(expr.value, "big")
     else:
-        raise InvalidLiteral(f"Invalid input for int256: {in_arg}", expr)
-
-
-@signature(("bool", "int128", "int256", "uint8", "uint256", "bytes32", "Bytes", "address"), "*")
-def to_decimal(expr, args, kwargs, context):
-    in_arg = args[0]
-    input_type, _ = get_type(in_arg)
-
-    if input_type == "Bytes":
-        if in_arg.typ.maxlen > 32:
-            raise TypeMismatch(
-                f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to decimal",
-                expr,
-            )
-        # use byte_array_to_num(int128) because it is cheaper to clamp int128
-        num = byte_array_to_num(in_arg, "int128")
-        return LLLnode.from_list(
-            ["mul", num, DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr)
-        )
-
+        # Int, Decimal
+        val = int(expr.value)
+    (lo, hi) = int_bounds(int_info.is_signed, int_info.bits)
+    if not (lo <= val <= hi):
+        raise InvalidLiteral("Number out of range", expr)
+    return LLLnode.from_list(val, typ=out_typ)
+
+
+def _literal_decimal(expr, out_typ):
+    # TODO: possible to reuse machinery from expr.py?
+    if isinstance(expr, vy_ast.Hex):
+        val = decimal.Decimal(int(expr.value, 16))
     else:
-        if input_type == "uint256":
-            if in_arg.typ.is_literal:
-                if not SizeLimits.in_bounds("int128", (in_arg.value * DECIMAL_DIVISOR)):
-                    raise InvalidLiteral(
-                        f"Number out of range: {in_arg.value}",
-                        expr,
-                    )
-                else:
-                    return LLLnode.from_list(
-                        ["mul", in_arg, DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr)
-                    )
-            else:
-                return LLLnode.from_list(
-                    [
-                        "uclample",
-                        ["mul", in_arg, DECIMAL_DIVISOR],
-                        ["mload", MemoryPositions.MAXDECIMAL],
-                    ],
-                    typ=BaseType("decimal"),
-                    pos=getpos(expr),
-                )
-
-        elif input_type == "address":
-            return LLLnode.from_list(
-                [
-                    "mul",
-                    ["signextend", 15, ["and", in_arg, (SizeLimits.ADDRSIZE - 1)]],
-                    DECIMAL_DIVISOR,
-                ],
-                typ=BaseType("decimal"),
-                pos=getpos(expr),
-            )
-
-        elif input_type == "bytes32":
-            if in_arg.typ.is_literal:
-                if not SizeLimits.in_bounds("int128", (in_arg.value * DECIMAL_DIVISOR)):
-                    raise InvalidLiteral(
-                        f"Number out of range: {in_arg.value}",
-                        expr,
-                    )
-                else:
-                    return LLLnode.from_list(
-                        ["mul", in_arg, DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr)
-                    )
-            else:
-                return LLLnode.from_list(
-                    [
-                        "clamp",
-                        ["mload", MemoryPositions.MINDECIMAL],
-                        ["mul", in_arg, DECIMAL_DIVISOR],
-                        ["mload", MemoryPositions.MAXDECIMAL],
-                    ],
-                    typ=BaseType("decimal"),
-                    pos=getpos(expr),
-                )
-
-        elif input_type == "int256":
-            # cast in_arg so clamp_basetype works
-            in_arg = LLLnode.from_list(in_arg, typ="int128")
-            return LLLnode.from_list(
-                ["mul", clamp_basetype(in_arg), DECIMAL_DIVISOR],
-                typ=BaseType("decimal"),
-                pos=getpos(expr),
-            )
-
-        elif input_type in ("uint8", "int128", "bool"):
-            return LLLnode.from_list(
-                ["mul", in_arg, DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr)
-            )
+        val = decimal.Decimal(expr.value)  # should work for Int, Decimal
 
-        else:
-            raise InvalidLiteral(f"Invalid input for decimal: {in_arg}", expr)
+    val = val * DECIMAL_DIVISOR
+
+    (lo, hi) = (SizeLimits.MINDECIMAL, SizeLimits.MAXDECIMAL)
+    if not (lo <= val <= hi):
+        raise InvalidLiteral("Number out of range", expr)
+
+    # sanity check type checker did its job
+    assert math.ceil(val) == math.floor(val)
+
+    return LLLnode.from_list(int(val), typ=out_typ)
+
+
+# any base type or bytes/string
+@_input_types("int", "decimal", "bytes_m", "address", "bool", "bytes", "string")
+def to_bool(expr, arg, out_typ):
+    _check_bytes(expr, arg, out_typ, 32)  # should we restrict to Bytes[1]?
+
+    if isinstance(arg.typ, ByteArrayType):
+        # no clamp. checks for any nonzero bytes.
+        arg = _bytes_to_num(arg, out_typ, signed=False)
+
+    # NOTE: for decimal, the behavior is x != 0.0,
+    # (we do not issue an `sdiv DECIMAL_DIVISOR`)
+
+    return LLLnode.from_list(["iszero", ["iszero", arg]], typ=out_typ)
+
+
+# special clamp for uint/sint conversions
+# uint -> sint requires input < sint::max_value
+# sint -> uint requires input >= 0
+# these are both equivalent to checking that the top bit is set
+# (e.g. for uint8 that the 8th bit is set, same for int8)
+def _signedness_clamp(arg, bits):
+    return int_clamp(arg, bits=bits - 1, signed=False)
+
+
+# clamp for dealing with conversions between numeric types (from arg to dst)
+def _num_clamp(arg, dst_info, arg_info):
+    if dst_info.is_signed != arg_info.is_signed:
+        arg = _signedness_clamp(arg, arg_info.bits)
+
+    # if, not elif (could be two clamps!)
+    # TODO is it possible to make this more efficient?
+    if dst_info.bits < arg_info.bits:
+        arg = int_clamp(arg, dst_info.bits, dst_info.is_signed)
+
+    return arg
 
 
-@signature(("int128", "int256", "uint8", "uint256", "address", "Bytes", "bool", "decimal"), "*")
-def to_bytes32(expr, args, kwargs, context):
-    in_arg = args[0]
-    input_type, _len = get_type(in_arg)
+@_input_types("int", "bytes_m", "decimal", "bytes", "address", "bool")
+def to_int(expr, arg, out_typ):
 
-    if input_type == "Bytes":
-        if _len > 32:
-            raise TypeMismatch(
-                f"Unable to convert bytes[{_len}] to bytes32, max length is too " "large."
-            )
+    int_info = out_typ._int_info
 
-        with in_arg.cache_when_complex("bytes") as (b1, in_arg):
-            op = load_op(in_arg.location)
-            ofst = wordsize(in_arg.location) * DYNAMIC_ARRAY_OVERHEAD
-            bytes_val = [op, ["add", in_arg, ofst]]
+    assert int_info.bits % 8 == 0
+    _check_bytes(expr, arg, out_typ, 32)
 
-            # zero out any dirty bytes (which can happen in the last
-            # word of a bytearray)
-            len_ = get_bytearray_length(in_arg)
-            num_zero_bits = LLLnode.from_list(["mul", ["sub", 32, len_], 8])
-            with num_zero_bits.cache_when_complex("bits") as (b2, num_zero_bits):
-                ret = shl(num_zero_bits, shr(num_zero_bits, bytes_val))
-                ret = b1.resolve(b2.resolve(ret))
+    if isinstance(expr, vy_ast.Constant):
+        return _literal_int(expr, out_typ)
 
+    elif isinstance(arg.typ, ByteArrayType):
+        arg_typ = arg.typ
+        arg = _bytes_to_num(arg, out_typ, signed=int_info.is_signed)
+        if arg_typ.maxlen * 8 > int_info.bits:
+            arg = int_clamp(arg, int_info.bits, signed=int_info.is_signed)
+
+    elif is_bytes_m_type(arg.typ):
+        arg_info = arg.typ._bytes_info
+        arg = _bytes_to_num(arg, out_typ, signed=int_info.is_signed)
+        if arg_info.m_bits > int_info.bits:
+            arg = int_clamp(arg, int_info.bits, signed=int_info.is_signed)
+
+    elif is_decimal_type(arg.typ):
+        arg_info = arg.typ._decimal_info
+        arg = _fixed_to_int(arg, out_typ, decimals=arg_info.decimals)
+        arg = _num_clamp(arg, int_info, arg_info)
+
+    elif is_integer_type(arg.typ):
+        arg_info = arg.typ._int_info
+        arg = _num_clamp(arg, int_info, arg_info)
+
+    elif is_base_type(arg.typ, "address"):
+        if int_info.is_signed:
+            # TODO if possible, refactor to move this validation close to the entry of the function
+            _FAIL(arg.typ, out_typ, expr)
+        if int_info.bits > 160:
+            arg = int_clamp(arg, 160, signed=False)
+
+    return LLLnode.from_list(arg, typ=out_typ)
+
+
+@_input_types("int", "bool", "bytes_m", "bytes")
+def to_decimal(expr, arg, out_typ):
+    # question: is converting from Bytes to decimal allowed?
+    _check_bytes(expr, arg, out_typ, max_bytes_allowed=16)
+
+    if isinstance(expr, vy_ast.Constant):
+        return _literal_decimal(expr, out_typ)
+
+    if isinstance(arg.typ, ByteArrayType):
+        arg_typ = arg.typ
+        arg = _bytes_to_num(arg, out_typ, signed=True)
+        # TODO revisit this condition once we have more decimal types
+        # and decimal bounds expand
+        # will be something like: if info.m_bits > 168
+        if arg_typ.maxlen * 8 > 128:
+            arg = LLLnode.from_list(arg, typ=out_typ)
+            arg = clamp_basetype(arg)
+        return LLLnode.from_list(arg, typ=out_typ)
+
+    elif is_bytes_m_type(arg.typ):
+        info = arg.typ._bytes_info
+        arg = _bytes_to_num(arg, out_typ, signed=True)
+        # TODO revisit this condition once we have more decimal types
+        # and decimal bounds expand
+        # will be something like: if info.m_bits > 168
+        if info.m_bits > 128:
+            arg = LLLnode.from_list(arg, typ=out_typ)
+            arg = clamp_basetype(arg)
+
+        return LLLnode.from_list(arg, typ=out_typ)
+
+    # for the clamp, pretend it's int128 because int128 clamps are cheaper
+    # (and then multiply into the decimal base afterwards)
+    elif is_integer_type(arg.typ):
+        int_info = arg.typ._int_info
+        # TODO revisit this condition once we have more decimal types
+        # and decimal bounds expand. (note that right now decimal bounds
+        # are -2**127 and 2**127 - 1).
+        # will be something like: if info.m_bits > 168
+        # TODO this should probably use _num_clamp instead
+        if int_info.bits > 128:
+            arg = int_clamp(arg, 128, signed=True)
+
+    elif is_base_type(arg.typ, "bool"):
+        pass
     else:
-        # literal
-        ret = in_arg
+        raise CompilerPanic("unreachable")  # pragma: notest
+
+    return _int_to_fixed(arg, out_typ)
+
+
+@_input_types("int", "decimal", "bytes_m", "address", "bytes", "bool")
+def to_bytes_m(expr, arg, out_typ):
+    out_info = out_typ._bytes_info
+    _check_bytes(expr, arg, out_typ, max_bytes_allowed=out_info.m)
 
-    return LLLnode.from_list(ret, typ="bytes32", pos=getpos(expr))
+    if isinstance(arg.typ, ByteArrayType):
+        bytes_val = load_word(bytes_data_ptr(arg))
 
+        # zero out any dirty bytes (which can happen in the last
+        # word of a bytearray)
+        len_ = get_bytearray_length(arg)
+        num_zero_bits = LLLnode.from_list(["mul", ["sub", 32, len_], 8])
+        with num_zero_bits.cache_when_complex("bits") as (b, num_zero_bits):
+            arg = shl(num_zero_bits, shr(num_zero_bits, bytes_val))
+            arg = b.resolve(arg)
 
-@signature(("bytes32", "uint256"), "*")
-def to_address(expr, args, kwargs, context):
-    # cast to output type so clamp_basetype works
-    lll_node = LLLnode.from_list(args[0], typ="address")
-    return LLLnode.from_list(clamp_basetype(lll_node), typ=BaseType("address"), pos=getpos(expr))
+    elif is_integer_type(arg.typ) or is_base_type(arg.typ, "address"):
+        int_bits = arg.typ._int_info.bits
 
+        if out_info.m_bits < int_bits:
+            # question: allow with runtime clamp?
+            # arg = int_clamp(m_bits, signed=int_info.signed)
+            _FAIL(arg.typ, out_typ, expr)
+
+        arg = shl(256 - out_info.m_bits, arg)
+
+    elif is_bytes_m_type(arg.typ):
+        arg_info = arg.typ._bytes_info
+        # clamp if it's a downcast
+        if arg_info.m > out_info.m:
+            arg = bytes_clamp(arg, out_info.m)
 
-def _to_bytelike(expr, args, kwargs, context, bytetype):
-    if bytetype == "String":
-        ReturnType = StringType
-    elif bytetype == "Bytes":
-        ReturnType = ByteArrayType
     else:
-        raise TypeMismatch(f"Invalid {bytetype} supplied")
+        # bool, decimal
+        arg = shl(256 - out_info.m_bits, arg)  # question: is this right?
+
+    return LLLnode.from_list(arg, typ=out_typ)
 
-    in_arg = args[0]
-    if in_arg.typ.maxlen > args[1].slice.value.n:
-        raise TypeMismatch(
-            f"Cannot convert as input {bytetype} are larger than max length",
-            expr,
-        )
 
-    return LLLnode(
-        value=in_arg.value,
-        args=in_arg.args,
-        typ=ReturnType(in_arg.typ.maxlen),
-        pos=getpos(expr),
-        location=in_arg.location,
-    )
+@_input_types("bytes_m", "int", "bytes")
+def to_address(expr, arg, out_typ):
+    # question: should this be allowed?
+    if is_integer_type(arg.typ):
+        if arg.typ._int_info.is_signed:
+            _FAIL(arg.typ, out_typ, expr)
 
+    # TODO once we introduce uint160, we can just check that arg
+    # is unsigned, and then call to_int(expr, arg, uint160) because
+    # the logic is equivalent.
 
-@signature(("Bytes"), "*")
-def to_string(expr, args, kwargs, context):
-    return _to_bytelike(expr, args, kwargs, context, bytetype="String")
+    # disallow casting from Bytes[N>20]
+    _check_bytes(expr, arg, out_typ, 32)
 
+    if isinstance(arg.typ, ByteArrayType):
+        arg_typ = arg.typ
+        arg = _bytes_to_num(arg, out_typ, signed=False)
+        # clamp after shift
+        if arg_typ.maxlen > 20:
+            arg = int_clamp(arg, 160, signed=False)
 
-@signature(("String"), "*")
-def to_bytes(expr, args, kwargs, context):
-    return _to_bytelike(expr, args, kwargs, context, bytetype="Bytes")
+    if is_bytes_m_type(arg.typ):
+        info = arg.typ._bytes_info
+        arg = _bytes_to_num(arg, out_typ, signed=False)
+
+        # clamp after shift
+        if info.m > 20:
+            arg = int_clamp(arg, 160, signed=False)
+
+    elif is_integer_type(arg.typ):
+        arg_info = arg.typ._int_info
+        if arg_info.bits > 160 or arg_info.is_signed:
+            arg = int_clamp(arg, 160, signed=False)
+
+    return LLLnode.from_list(arg, typ=out_typ)
+
+
+# question: should we allow bytesM -> String?
+@_input_types("bytes")
+def to_string(expr, arg, out_typ):
+    _check_bytes(expr, arg, out_typ, out_typ.maxlen)
+
+    # NOTE: this is a pointer cast
+    return LLLnode.from_list(arg, typ=out_typ)
+
+
+@_input_types("string")
+def to_bytes(expr, arg, out_typ):
+    _check_bytes(expr, arg, out_typ, out_typ.maxlen)
+
+    # TODO: more casts
+
+    # NOTE: this is a pointer cast
+    return LLLnode.from_list(arg, typ=out_typ)
 
 
 def convert(expr, context):
     if len(expr.args) != 2:
         raise StructureException("The convert function expects two parameters.", expr)
-    if isinstance(expr.args[1], vy_ast.Str):
-        warnings.warn(
-            "String parameter has been removed (see VIP1026). " "Use a vyper type instead.",
-            DeprecationWarning,
-        )
-
-    if isinstance(expr.args[1], vy_ast.Name):
-        output_type = expr.args[1].id
-    elif isinstance(expr.args[1], (vy_ast.Subscript)) and isinstance(
-        expr.args[1].value, (vy_ast.Name)
-    ):
-        output_type = expr.args[1].value.id
-    else:
-        raise StructureException("Invalid conversion type, use valid Vyper type.", expr)
 
-    if output_type in CONVERSION_TABLE:
-        return CONVERSION_TABLE[output_type](expr, context)
-    else:
-        raise StructureException(f"Conversion to {output_type} is invalid.", expr)
-
-
-CONVERSION_TABLE = {
-    "bool": to_bool,
-    "int128": to_int128,
-    "int256": to_int256,
-    "uint8": to_uint8,
-    "uint256": to_uint256,
-    "decimal": to_decimal,
-    "bytes32": to_bytes32,
-    "address": to_address,
-    "String": to_string,
-    "Bytes": to_bytes,
-}
+    arg_ast = expr.args[0]
+    arg = Expr(arg_ast, context).lll_node
+    out_typ = context.parse_type(expr.args[1])
+
+    if isinstance(arg.typ, BaseType):
+        arg = unwrap_location(arg)
+    with arg.cache_when_complex("arg") as (b, arg):
+        if is_base_type(out_typ, "bool"):
+            ret = to_bool(arg_ast, arg, out_typ)
+        elif is_base_type(out_typ, "address"):
+            ret = to_address(arg_ast, arg, out_typ)
+        elif is_integer_type(out_typ):
+            ret = to_int(arg_ast, arg, out_typ)
+        elif is_bytes_m_type(out_typ):
+            ret = to_bytes_m(arg_ast, arg, out_typ)
+        elif is_decimal_type(out_typ):
+            ret = to_decimal(arg_ast, arg, out_typ)
+        elif isinstance(out_typ, ByteArrayType):
+            ret = to_bytes(arg_ast, arg, out_typ)
+        elif isinstance(out_typ, StringType):
+            ret = to_string(arg_ast, arg, out_typ)
+        else:
+            raise StructureException(f"Conversion to {out_typ} is invalid.", arg_ast)
+
+        ret = b.resolve(ret)
+
+    return LLLnode.from_list(ret, pos=getpos(expr))
diff --git a/vyper/builtin_functions/functions.py b/vyper/builtin_functions/functions.py
index 736ab8b8..823f657d 100644
--- a/vyper/builtin_functions/functions.py
+++ b/vyper/builtin_functions/functions.py
@@ -24,6 +24,7 @@ from vyper.codegen.core import (
     getpos,
     lll_tuple_from_args,
     load_op,
+    promote_signed_int,
     unwrap_location,
 )
 from vyper.codegen.expr import Expr
@@ -167,61 +168,25 @@ class Ceil(_SimpleBuiltinFunction):
 
 class Convert:
 
-    # TODO this is just a wireframe, expand it with complete functionality
-    # https://github.com/vyperlang/vyper/issues/1093
-
     _id = "convert"
 
     def fetch_call_return(self, node):
         validate_call_args(node, 2)
         target_type = get_type_from_annotation(node.args[1], DataLocation.MEMORY)
-
         validate_expected_type(node.args[0], ValueTypeDefinition())
+
+        # block conversions between same type
         try:
             validate_expected_type(node.args[0], target_type)
         except VyperException:
             pass
         else:
-            # TODO remove this once it's possible in parser
             if not isinstance(target_type, Uint256Definition):
                 raise InvalidType(f"Value and target type are both '{target_type}'", node)
 
-        # TODO!
-        # try:
-        #     validation_fn = getattr(self, f"validate_to_{target_type._id}")
-        # except AttributeError:
-        #     raise InvalidType(
-        #         f"Unsupported destination type '{target_type}'", node.args[1]
-        #     ) from None
-
-        # validation_fn(initial_type)
-
+        # note: more type conversion validation happens in convert.py
         return target_type
 
-    def validate_to_bool(self, initial_type):
-        pass
-
-    def validate_to_decimal(self, initial_type):
-        pass
-
-    def validate_to_int128(self, initial_type):
-        pass
-
-    def validate_to_uint256(self, initial_type):
-        pass
-
-    def validate_to_bytes32(self, initial_type):
-        pass
-
-    def validate_to_string(self, initial_type):
-        pass
-
-    def validate_to_bytes(self, initial_type):
-        pass
-
-    def validate_to_address(self, initial_type):
-        pass
-
     def build_LLL(self, expr, context):
         return convert(expr, context)
 
@@ -1607,7 +1572,7 @@ class Abs(_SimpleBuiltinFunction):
             [
                 "if",
                 ["slt", "orig", 0],
-                # CMC 2022-02-05 pretty sure this assertion is pointless.
+                # clamp orig != -2**255 (because it maps to itself under negation)
                 ["seq", ["assert", ["ne", "orig", ["sub", 0, "orig"]]], ["sub", 0, "orig"]],
                 "orig",
             ],
@@ -1757,7 +1722,7 @@ class _UnsafeMath:
             # wrap for ops which could under/overflow
             if int_info.is_signed:
                 # e.g. int128 -> (signextend 15 (add x y))
-                ret = ["signextend", int_info.bits // 8 - 1, ret]
+                ret = promote_signed_int(ret, int_info.bits)
             else:
                 # e.g. uint8 -> (mod (add x y) 256)
                 # TODO mod_bound could be a really large literal
diff --git a/vyper/builtin_functions/signatures.py b/vyper/builtin_functions/signatures.py
index 01f8a7cb..f4b68097 100644
--- a/vyper/builtin_functions/signatures.py
+++ b/vyper/builtin_functions/signatures.py
@@ -64,7 +64,6 @@ def process_arg(index, arg, expected_arg_typelist, function_name, context):
             if isinstance(sub.typ, StringType):
                 return sub
         else:
-            # Does not work for unit-endowed types inside compound types, e.g. timestamp[2]
             parsed_expected_type = context.parse_type(vy_ast.parse_to_ast(expected_arg)[0].value)
             if isinstance(parsed_expected_type, BaseType):
                 vsub = vsub or Expr.parse_value_expr(arg, context)
@@ -83,6 +82,7 @@ def process_arg(index, arg, expected_arg_typelist, function_name, context):
                 vsub = vsub or Expr(arg, context).lll_node
                 if vsub.typ == parsed_expected_type:
                     return Expr(arg, context).lll_node
+
     if len(expected_arg_typelist) == 1:
         raise TypeMismatch(f"Expecting {expected_arg} for argument {index} of {function_name}", arg)
     else:
@@ -91,57 +91,6 @@ def process_arg(index, arg, expected_arg_typelist, function_name, context):
         )
 
 
-def signature(*argz, **kwargz):
-    def decorator(f):
-        @functools.wraps(f)
-        def g(element, context):
-            function_name = element.func.id
-            if len(element.args) > len(argz):
-                raise StructureException(
-                    f"Expected {len(argz)} arguments for {function_name}, "
-                    f"got {len(element.args)}",
-                    element,
-                )
-            subs = []
-            for i, expected_arg in enumerate(argz):
-                if len(element.args) > i:
-                    subs.append(
-                        process_arg(
-                            i + 1,
-                            element.args[i],
-                            expected_arg,
-                            function_name,
-                            context,
-                        )
-                    )
-                elif isinstance(expected_arg, Optional):
-                    subs.append(expected_arg.default)
-                else:
-                    raise StructureException(
-                        f"Not enough arguments for function: {element.func.id}", element
-                    )
-            kwsubs = {}
-            element_kw = {k.arg: k.value for k in element.keywords}
-            for k, expected_arg in kwargz.items():
-                if k not in element_kw:
-                    if isinstance(expected_arg, Optional):
-                        kwsubs[k] = expected_arg.default
-                    else:
-                        raise StructureException(
-                            f"Function {function_name} requires argument {k}", element
-                        )
-                else:
-                    kwsubs[k] = process_arg(k, element_kw[k], expected_arg, function_name, context)
-            for k, _arg in element_kw.items():
-                if k not in kwargz:
-                    raise StructureException(f"Unexpected argument: {k}", element)
-            return f(element, subs, kwsubs, context)
-
-        return g
-
-    return decorator
-
-
 def validate_inputs(wrapped_fn):
     """
     Validate input arguments on builtin functions.
diff --git a/vyper/codegen/core.py b/vyper/codegen/core.py
index 8918d1ab..c9263138 100644
--- a/vyper/codegen/core.py
+++ b/vyper/codegen/core.py
@@ -1,4 +1,4 @@
-from decimal import Context, Decimal, setcontext
+from decimal import Context, setcontext
 
 from vyper import ast as vy_ast
 from vyper.codegen.lll_node import Encoding, LLLnode
@@ -20,7 +20,6 @@ from vyper.evm.opcodes import version_check
 from vyper.exceptions import (
     CompilerPanic,
     DecimalOverrideException,
-    InvalidLiteral,
     StructureException,
     TypeCheckFailure,
     TypeMismatch,
@@ -53,29 +52,6 @@ def check_external_call(call_lll):
     return ["if", ["iszero", call_lll], propagate_revert_lll]
 
 
-# Get a decimal number as a fraction with denominator multiple of 10
-def get_number_as_fraction(expr, context):
-    literal = Decimal(expr.value)
-    sign, digits, exponent = literal.as_tuple()
-
-    if exponent < -10:
-        raise InvalidLiteral(
-            f"`decimal` literal cannot have more than 10 decimal places: {literal}", expr
-        )
-
-    sign = -1 if sign == 1 else 1  # Positive Decimal has `sign` of 0, negative `sign` of 1
-    # Decimal `digits` is a tuple of each digit, so convert to a regular integer
-    top = int(Decimal((0, digits, 0)))
-    top = sign * top * 10 ** (exponent if exponent > 0 else 0)  # Convert to a fixed point integer
-    bottom = 1 if exponent > 0 else 10 ** abs(exponent)  # Make denominator a power of 10
-    assert Decimal(top) / Decimal(bottom) == literal  # Sanity check
-
-    # TODO: Would be best to raise >10 decimal place exception here
-    #       (unless Decimal is used more widely)
-
-    return expr.node_source_code, top, bottom
-
-
 # cost per byte of the identity precompile
 def _identity_gas_bound(num_bytes):
     return GAS_IDENTITY + GAS_IDENTITYWORD * (ceil32(num_bytes) // 32)
@@ -578,6 +554,7 @@ def get_element_ptr(parent, key, pos, array_bounds_check=True):
         return b.resolve(ret)
 
 
+# TODO phase this out - make private and use load_word instead
 def load_op(location):
     if location == "memory":
         return "mload"
@@ -595,6 +572,7 @@ def load_op(location):
     raise CompilerPanic(f"unreachable {location}")  # pragma: notest
 
 
+# TODO phase this out - make private and use store_word instead
 def store_op(location):
     if location == "memory":
         return "mstore"
@@ -605,10 +583,18 @@ def store_op(location):
     raise CompilerPanic(f"unreachable {location}")  # pragma: notest
 
 
+def load_word(ptr: LLLnode) -> LLLnode:
+    return LLLnode.from_list([load_op(ptr.location), ptr])
+
+
+def store_word(ptr: LLLnode, val: LLLnode) -> LLLnode:
+    return LLLnode.from_list([store_op(ptr.location), ptr, val])
+
+
 # Unwrap location
 def unwrap_location(orig):
     if orig.location in ("memory", "storage", "calldata", "data", "immutables"):
-        return LLLnode.from_list([load_op(orig.location), orig], typ=orig.typ)
+        return LLLnode.from_list(load_word(orig), typ=orig.typ)
     else:
         # CMC 20210909 TODO double check if this branch can be removed
         if orig.value == "~empty":
@@ -1026,17 +1012,35 @@ def int_clamp(lll_node, bits, signed=False):
     """
     if bits >= 256:
         raise CompilerPanic(f"invalid clamp: {bits}>=256 ({lll_node})")  # pragma: notest
-    if signed:
-        # example for bits==128:
-        # if _val is in bounds,
-        # _val >>> 127 == 0 for positive _val
-        # _val >>> 127 == -1 for negative _val
-        # -1 and 0 are the only numbers which are unchanged by sar,
-        # so sar'ing (_val>>>127) one more bit should leave it unchanged.
-        assertion = ["assert", ["eq", sar(bits - 1, "val"), sar(bits, "val")]]
-    else:
-        assertion = ["assert", ["iszero", shr(bits, "val")]]
+    with lll_node.cache_when_complex("val") as (b, val):
+        if signed:
+            # example for bits==128:
+            # promote_signed_int(val, bits) is the "canonical" version of val
+            # if val is in bounds, the bits above bit 128 should be equal.
+            # (this works for both val >= 0 and val < 0. in the first case,
+            # all upper bits should be 0 if val is a valid int128,
+            # in the latter case, all upper bits should be 1.)
+            assertion = ["assert", ["eq", val, promote_signed_int(val, bits)]]
+        else:
+            assertion = ["assert", ["iszero", shr(bits, val)]]
 
-    ret = ["with", "val", lll_node, ["seq", assertion, "val"]]
+        ret = b.resolve(["seq", assertion, val])
 
+    # TODO fix this annotation
     return LLLnode.from_list(ret, annotation=f"int_clamp {lll_node.typ}")
+
+
+def bytes_clamp(lll_node: LLLnode, n_bytes: int) -> LLLnode:
+    if not (0 < n_bytes <= 32):
+        raise CompilerPanic(f"bad type: bytes{n_bytes}")
+    with lll_node.cache_when_complex("val") as (b, val):
+        assertion = ["assert", ["iszero", shl(n_bytes * 8, val)]]
+        ret = b.resolve(["seq", assertion, val])
+    return LLLnode.from_list(ret, annotation=f"bytes{n_bytes}_clamp")
+
+
+# e.g. for int8, promote 255 to -1
+def promote_signed_int(x, bits):
+    assert bits % 8 == 0
+    ret = ["signextend", bits // 8 - 1, x]
+    return LLLnode.from_list(ret, annotation=f"promote int{bits}")
diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py
index a419ff7f..9bbfa8d9 100644
--- a/vyper/codegen/expr.py
+++ b/vyper/codegen/expr.py
@@ -1,5 +1,5 @@
+import decimal
 import math
-from decimal import Decimal
 
 from vyper import ast as vy_ast
 from vyper.codegen import external_call, self_call
@@ -8,7 +8,6 @@ from vyper.codegen.core import (
     ensure_in_memory,
     get_dyn_array_count,
     get_element_ptr,
-    get_number_as_fraction,
     getpos,
     load_op,
     make_setter,
@@ -100,7 +99,7 @@ def calculate_largest_power(a: int, num_bits: int, is_signed: bool) -> int:
 
     # NOTE: There is an edge case if `a` were left signed where the following
     #       operation would not work (`ln(a)` is undefined if `a <= 0`)
-    b = int(Decimal(value_bits) / (Decimal(a).ln() / Decimal(2).ln()))
+    b = int(decimal.Decimal(value_bits) / (decimal.Decimal(a).ln() / decimal.Decimal(2).ln()))
     if b <= 1:
         return 1  # Value is assumed to be in range, therefore power of 1 is max
 
@@ -156,7 +155,7 @@ def calculate_largest_base(b: int, num_bits: int, is_signed: bool) -> int:
         return 2 ** value_bits - 1  # Maximum value for type
 
     # Estimate (up to ~39 digits precision required)
-    a = math.ceil(2 ** (Decimal(value_bits) / Decimal(b)))
+    a = math.ceil(2 ** (decimal.Decimal(value_bits) / decimal.Decimal(b)))
     # Do a bit of iteration to ensure we have the exact number
     num_iterations = 0
     while (a + 1) ** b < 2 ** value_bits:
@@ -219,13 +218,13 @@ class Expr:
             )
 
     def parse_Decimal(self):
-        numstring, num, den = get_number_as_fraction(self.expr, self.context)
-        if not (SizeLimits.MIN_INT128 * den <= num <= SizeLimits.MAX_INT128 * den):
-            return
-        if DECIMAL_DIVISOR % den:
-            return
+        val = self.expr.value
+        # sanity check that type checker did its job
+        assert isinstance(val, decimal.Decimal)
+        assert SizeLimits.MINDECIMAL <= val <= SizeLimits.MAXDECIMAL
+
         return LLLnode.from_list(
-            num * DECIMAL_DIVISOR // den,
+            int(val * DECIMAL_DIVISOR),
             typ=BaseType("decimal", is_literal=True),
             pos=getpos(self.expr),
         )
diff --git a/vyper/codegen/types/types.py b/vyper/codegen/types/types.py
index 7636a8a1..7923d531 100644
--- a/vyper/codegen/types/types.py
+++ b/vyper/codegen/types/types.py
@@ -20,7 +20,18 @@ from vyper.abi_types import (
     ABIType,
 )
 from vyper.exceptions import ArgumentException, CompilerPanic, InvalidType
-from vyper.utils import BASE_TYPES, ceil32
+from vyper.utils import ceil32
+
+# Available base types
+UNSIGNED_INTEGER_TYPES = {"uint8", "uint256"}
+SIGNED_INTEGER_TYPES = {"int128", "int256"}
+INTEGER_TYPES = UNSIGNED_INTEGER_TYPES | SIGNED_INTEGER_TYPES
+
+BYTES_M_TYPES = {"bytes32"}
+DECIMAL_TYPES = {"decimal"}
+
+
+BASE_TYPES = INTEGER_TYPES | BYTES_M_TYPES | DECIMAL_TYPES | {"bool", "address"}
 
 
 # Data structure for a type
@@ -63,12 +74,29 @@ class NodeType(abc.ABC):
         return r // 32
 
 
+# helper functions for handling old base types which are just strings
+# in the future these can be reified with new type system
+
+
 @dataclass
 class IntegerTypeInfo:
     is_signed: bool
     bits: int
 
 
+@dataclass
+class DecimalTypeInfo:
+    bits: int
+    decimals: int
+    is_signed: bool  # always true for now but may change
+
+
+@dataclass
+class BytesMTypeInfo:
+    m: int
+    m_bits: int  # m_bits == m * 8, just convenient to have
+
+
 _int_parser = re.compile("^(u?)int([0-9]+)$")
 
 
@@ -87,19 +115,38 @@ def parse_integer_typeinfo(typename: str) -> IntegerTypeInfo:
     )
 
 
+def is_bytes_m_type(t: "NodeType") -> bool:
+    return isinstance(t, BaseType) and t.typ.startswith("bytes")
+
+
+def parse_bytes_m_info(typename: str) -> BytesMTypeInfo:
+    m = int(typename[len("bytes") :])
+    return BytesMTypeInfo(m=m, m_bits=m * 8)
+
+
+def is_decimal_type(t: "NodeType") -> bool:
+    return isinstance(t, BaseType) and t.typ == "decimal"
+
+
+def parse_decimal_info(typename: str) -> DecimalTypeInfo:
+    # in the future, this will actually do parsing
+    assert typename == "decimal"
+    return DecimalTypeInfo(bits=168, decimals=10, is_signed=True)
+
+
 def _basetype_to_abi_type(t: "BaseType") -> ABIType:
     if is_integer_type(t):
-        typinfo = parse_integer_typeinfo(t.typ)
-        return ABI_GIntM(typinfo.bits, typinfo.is_signed)
+        info = t._int_info
+        return ABI_GIntM(info.bits, info.is_signed)
+    if is_decimal_type(t):
+        info = t._decimal_info
+        return ABI_FixedMxN(info.bits, info.decimals, signed=True)
+    if is_bytes_m_type(t):
+        return ABI_BytesM(t._bytes_info.m)
     if t.typ == "address":
         return ABI_Address()
-    if t.typ == "bytes32":
-        # TODO must generalize to more bytes types
-        return ABI_BytesM(32)
     if t.typ == "bool":
         return ABI_Bool()
-    if t.typ == "decimal":
-        return ABI_FixedMxN(168, 10, True)
 
     raise InvalidType(f"Unrecognized type {t}")  # pragma: notest
 
@@ -112,6 +159,18 @@ class BaseType(NodeType):
         # change to property on LLLnode: `isinstance(self.value, int)`
         self.is_literal = is_literal
 
+        if is_integer_type(self):
+            self._int_info = parse_integer_typeinfo(typename)
+        if is_base_type(self, "address"):
+            self._int_info = IntegerTypeInfo(bits=160, is_signed=False)
+        # don't generate _int_info for bool,
+        # it doesn't really behave like an int in conversions
+        # and should have special handling in the codebase
+        if is_bytes_m_type(self):
+            self._bytes_info = parse_bytes_m_info(typename)
+        if is_decimal_type(self):
+            self._decimal_info = parse_decimal_info(typename)
+
     def eq(self, other):
         return self.typ == other.typ
 
@@ -439,16 +498,6 @@ def get_type_for_exact_size(n_bytes):
     return ByteArrayType(n_bytes - 32 * DYNAMIC_ARRAY_OVERHEAD)
 
 
-def get_type(input):
-    if not hasattr(input, "typ"):
-        typ, len = "num_literal", 32
-    elif hasattr(input.typ, "maxlen"):
-        typ, len = "Bytes", input.typ.maxlen
-    else:
-        typ, len = input.typ.typ, 32
-    return typ, len
-
-
 # Is a type representing a number?
 def is_numeric_type(typ):
     return isinstance(typ, BaseType) and typ.typ in (
diff --git a/vyper/lll/optimizer.py b/vyper/lll/optimizer.py
index 3be20438..f38d7a26 100644
--- a/vyper/lll/optimizer.py
+++ b/vyper/lll/optimizer.py
@@ -95,6 +95,12 @@ def apply_general_optimizations(node: LLLnode) -> LLLnode:
         argz = []
         value = ceil32(t.value)
 
+    # x >> 0 == x << 0 == x
+    elif node.value in ("shl", "shr", "sar") and get_int_at(argz, 0) == 0:
+        value = argz[1].value
+        annotation = argz[1].annotation
+        argz = argz[1].args
+
     elif node.value == "add" and get_int_at(argz, 0) == 0:
         value = argz[1].value
         annotation = argz[1].annotation
diff --git a/vyper/semantics/types/abstract.py b/vyper/semantics/types/abstract.py
index 8f1ee2f1..bab0ee5f 100644
--- a/vyper/semantics/types/abstract.py
+++ b/vyper/semantics/types/abstract.py
@@ -41,12 +41,18 @@ class ArrayValueAbstractType(AbstractDataType):
 
 
 class BytesAbstractType(AbstractDataType):
-    """Abstract data class for bytes types (bytes32, bytes[])."""
+    """Abstract data class for bytes types (bytes, bytesM)."""
 
     _description = "bytes"
     _id = "bytes"
 
 
+class BytesMAbstractType(BytesAbstractType):
+    """Abstract data class for fixed bytes types (bytes32, bytes4, ...)"""
+
+    _description = "bytes<M> where M<=32"
+
+
 class NumericAbstractType(AbstractDataType):
     """
     Abstract data class for numeric types (capable of arithmetic).
@@ -77,6 +83,7 @@ class UnsignedIntegerAbstractType(IntegerAbstractType):
     _id_list: Tuple = ("uint8", "uint256")
 
 
+# TODO rename me to DecimalAbstractType (confusion with BytesM - fixed bytes)
 class FixedAbstractType(NumericAbstractType):
     """
     Abstract data class for decimal numeric types.
diff --git a/vyper/semantics/types/value/bytes_fixed.py b/vyper/semantics/types/value/bytes_fixed.py
index c1b1ba2f..bc6477c8 100644
--- a/vyper/semantics/types/value/bytes_fixed.py
+++ b/vyper/semantics/types/value/bytes_fixed.py
@@ -1,11 +1,11 @@
 from vyper import ast as vy_ast
 from vyper.abi_types import ABI_BytesM, ABIType
 from vyper.exceptions import InvalidLiteral
-from vyper.semantics.types.abstract import BytesAbstractType
+from vyper.semantics.types.abstract import BytesMAbstractType
 from vyper.semantics.types.bases import BasePrimitive, BaseTypeDefinition, ValueTypeDefinition
 
 
-class Bytes32Definition(BytesAbstractType, ValueTypeDefinition):
+class Bytes32Definition(BytesMAbstractType, ValueTypeDefinition):
 
     # included for compatibility with bytes array methods
     _id = "bytes32"
diff --git a/vyper/utils.py b/vyper/utils.py
index 5c6cab2d..5e2c7edd 100644
--- a/vyper/utils.py
+++ b/vyper/utils.py
@@ -1,4 +1,5 @@
 import binascii
+import decimal
 import sys
 import traceback
 from typing import Dict, List, Union
@@ -73,7 +74,7 @@ def bytes_to_int(bytez):
 
 # Encodes an address using ethereum's checksum scheme
 def checksum_encode(addr):  # Expects an input of the form 0x<40 hex chars>
-    assert addr[:2] == "0x" and len(addr) == 42
+    assert addr[:2] == "0x" and len(addr) == 42, addr
     o = ""
     v = bytes_to_int(keccak256(addr[2:].lower().encode("utf-8")))
     for i, c in enumerate(addr[2:]):
@@ -148,7 +149,6 @@ class MemoryPositions:
 
 # Sizes of different data types. Used to clamp types.
 class SizeLimits:
-    ADDRSIZE = 2 ** 160
     MAX_INT128 = 2 ** 127 - 1
     MIN_INT128 = -(2 ** 127)
     MAX_INT256 = 2 ** 255 - 1
@@ -162,7 +162,7 @@ class SizeLimits:
     def in_bounds(cls, type_str, value):
         assert isinstance(type_str, str)
         if type_str == "decimal":
-            return float(cls.MINDECIMAL) <= value <= float(cls.MAXDECIMAL)
+            return decimal.Decimal(cls.MINDECIMAL) <= value <= decimal.Decimal(cls.MAXDECIMAL)
         if type_str == "uint8":
             return 0 <= value <= cls.MAX_UINT8
         elif type_str == "uint256":
@@ -235,9 +235,6 @@ VALID_LLL_MACROS = {
     "var_list",
 }
 
-# Available base types
-BASE_TYPES = {"int128", "int256", "decimal", "bytes32", "uint8", "uint256", "bool", "address"}
-
 
 def indent(text: str, indent_chars: Union[str, List[str]] = " ", level: int = 1) -> str:
     """


Test patch:
diff --git a/tests/parser/functions/test_convert_to_address.py b/tests/parser/functions/test_convert_to_address.py
index f454acd9..49ab3cd9 100644
--- a/tests/parser/functions/test_convert_to_address.py
+++ b/tests/parser/functions/test_convert_to_address.py
@@ -1,6 +1,10 @@
+from vyper.utils import checksum_encode
+
+
 def test_convert_from_bytes32(get_contract_with_gas_estimation):
     test_address = "0xF5D4020dCA6a62bB1efFcC9212AAF3c9819E30D7"
-    test_bytes = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xF5\xD4\x02\x0d\xCA\x6a\x62\xbB\x1e\xfF\xcC\x92\x12\xAA\xF3\xc9\x81\x9E\x30\xD7"  # noqa: E501
+    # left padded with zeroes
+    test_bytes = int(test_address, 16).to_bytes(20, "big").rjust(32, b"\00")
 
     test_bytes_to_address = """
 @external
@@ -13,8 +17,8 @@ def test_bytes_to_address(x: bytes32) -> address:
 
 
 def test_bytes32_clamping(get_contract, assert_tx_failed):
-    test_passing = ("ff" * 20).zfill(64)
-    test_fails = ("1" + "ff" * 20).zfill(64)
+    test_passing = (b"\xff" * 20).rjust(32, b"\x00")
+    test_fails = (b"\x01" + b"\xff" * 20).rjust(32, b"\x00")
 
     test_bytes_to_address = """
 @external
@@ -25,7 +29,7 @@ def foo(x: bytes32) -> address:
     c = get_contract(test_bytes_to_address)
 
     assert_tx_failed(lambda: c.foo(test_fails))
-    assert c.foo(test_passing) == "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF"
+    assert c.foo(test_passing) == checksum_encode("0x" + "ff" * 20)
 
 
 def test_convert_from_uint256(get_contract_with_gas_estimation):
@@ -55,4 +59,4 @@ def foo(x: uint256) -> address:
     c = get_contract(test_bytes_to_address)
 
     assert_tx_failed(lambda: c.foo(test_fails))
-    assert c.foo(test_passing) == "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF"
+    assert c.foo(test_passing) == checksum_encode("0x" + "ff" * 20)
diff --git a/tests/parser/functions/test_convert_to_bool.py b/tests/parser/functions/test_convert_to_bool.py
index 70ae17d6..72894097 100644
--- a/tests/parser/functions/test_convert_to_bool.py
+++ b/tests/parser/functions/test_convert_to_bool.py
@@ -35,7 +35,7 @@ def foo() -> bool:
 
 @external
 def hoo() -> bool:
-    return convert(-100.0, bool)
+    return convert(-0.1, bool)
 
 @external
 def goo() -> bool:
@@ -48,7 +48,7 @@ def foobar() -> bool:
 
 @external
 def hoonar() -> bool:
-    self.nar = -100.0
+    self.nar = -0.1
     return convert(self.nar, bool)
 
 @external
diff --git a/tests/parser/functions/test_convert_to_bytes32.py b/tests/parser/functions/test_convert_to_bytes32.py
index f1d575bb..c06183b1 100644
--- a/tests/parser/functions/test_convert_to_bytes32.py
+++ b/tests/parser/functions/test_convert_to_bytes32.py
@@ -55,9 +55,9 @@ def bytes_to_bytes32_from_smaller(inp: Bytes[10]) -> bytes32:
     """
 
     c = get_contract_with_gas_estimation(code)
-    assert c.int128_to_bytes32(1) == [bytes_helper("", 31) + b"\x01"] * 3
-    assert c.int256_to_bytes32(1) == [bytes_helper("", 31) + b"\x01"] * 3
-    assert c.uint256_to_bytes32(1) == [bytes_helper("", 31) + b"\x01"] * 3
+    assert c.int128_to_bytes32(1) == [int(1).to_bytes(32, "big")] * 3
+    assert c.int256_to_bytes32(1) == [int(1).to_bytes(32, "big")] * 3
+    assert c.uint256_to_bytes32(1) == [int(1).to_bytes(32, "big")] * 3
     assert (
         c.address_to_bytes32(w3.eth.accounts[0])
         == [bytes_helper("", 12) + w3.toBytes(hexstr=w3.eth.accounts[0])] * 2
@@ -68,7 +68,7 @@ def bytes_to_bytes32_from_smaller(inp: Bytes[10]) -> bytes32:
 
 def test_convert_from_address(get_contract_with_gas_estimation):
     test_address = "0xF5D4020dCA6a62bB1efFcC9212AAF3c9819E30D7"
-    test_bytes = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xF5\xD4\x02\x0d\xCA\x6a\x62\xbB\x1e\xfF\xcC\x92\x12\xAA\xF3\xc9\x81\x9E\x30\xD7"  # noqa: E501
+    test_bytes = int(test_address, 16).to_bytes(20, "big").rjust(32, b"\00")
 
     test_address_to_bytes = """
 @external
diff --git a/tests/parser/functions/test_convert_to_decimal.py b/tests/parser/functions/test_convert_to_decimal.py
index defcba4e..33da1ce6 100644
--- a/tests/parser/functions/test_convert_to_decimal.py
+++ b/tests/parser/functions/test_convert_to_decimal.py
@@ -72,9 +72,9 @@ def foo(bar: bytes32) -> decimal:
 
     c = get_contract_with_gas_estimation(code)
     assert c.foo(b"\x00" * 32) == 0.0
-    assert c.foo(b"\xff" * 32) == -1.0
-    assert c.foo((b"\x00" * 31) + b"\x01") == 1.0
-    assert c.foo((b"\x00" * 30) + b"\x01\x00") == 256.0
+    assert c.foo(b"\xff" * 32) == Decimal("-1e-10")
+    assert c.foo((b"\x00" * 31) + b"\x01") == Decimal("1e-10")
+    assert c.foo((b"\x00" * 30) + b"\x01\x00") == Decimal("256e-10")
 
 
 def test_convert_from_bytes32_overflow(get_contract_with_gas_estimation, assert_compile_failed):
@@ -94,22 +94,22 @@ def foo(bar: Bytes[5]) -> decimal:
     return convert(bar, decimal)
 
 @external
-def goo(bar: Bytes[32]) -> decimal:
+def goo(bar: Bytes[16]) -> decimal:
     return convert(bar, decimal)
     """
 
     c = get_contract_with_gas_estimation(code)
 
     assert c.foo(b"\x00\x00\x00\x00\x00") == 0.0
-    assert c.foo(b"\x00\x07\x5B\xCD\x15") == 123456789.0
+    assert c.foo(b"\x00\x07\x5B\xCD\x15") == Decimal("123456789e-10")
 
     assert c.goo(b"") == 0.0
     assert c.goo(b"\x00") == 0.0
-    assert c.goo(b"\x01") == 1.0
-    assert c.goo(b"\x00\x01") == 1.0
-    assert c.goo(b"\x01\x00") == 256.0
-    assert c.goo(b"\x01\x00\x00\x00\x01") == 4294967297.0
-    assert c.goo(b"\xff" * 32) == -1.0
+    assert c.goo(b"\x01") == Decimal("1e-10")
+    assert c.goo(b"\x00\x01") == Decimal("1e-10")
+    assert c.goo(b"\x01\x00") == Decimal("256e-10")
+    assert c.goo(b"\x01\x00\x00\x00\x01") == Decimal("4294967297e-10")
+    assert c.goo(b"\xff" * 16) == Decimal("-1.0e-10")
 
 
 def test_convert_from_too_many_bytes(get_contract_with_gas_estimation, assert_compile_failed):
@@ -137,44 +137,33 @@ def foobar() -> decimal:
     )
 
 
-def test_convert_from_address(get_contract_with_gas_estimation):
-    code = """
+def test_convert_from_address(get_contract, assert_compile_failed):
+    codes = [
+        """
 stor: address
-
+@external
+def conv_zero_stor() -> decimal:
+    self.stor = ZERO_ADDRESS
+    return convert(self.stor, decimal)
+""",
+        """
 @external
 def conv(param: address) -> decimal:
     return convert(param, decimal)
-
+""",
+        """
 @external
 def conv_zero_literal() -> decimal:
     return convert(ZERO_ADDRESS, decimal)
-
-@external
-def conv_zero_stor() -> decimal:
-    self.stor = ZERO_ADDRESS
-    return convert(self.stor, decimal)
-
+""",
+        """
 @external
 def conv_neg1_literal() -> decimal:
     return convert(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF, decimal)
-
-@external
-def conv_neg1_stor() -> decimal:
-    self.stor = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF
-    return convert(self.stor, decimal)
-    """
-
-    c = get_contract_with_gas_estimation(code)
-    assert c.conv(b"\x00" * 20) == 0.0
-    assert c.conv_zero_literal() == 0.0
-    assert c.conv_zero_stor() == 0.0
-
-    assert c.conv(b"\xff" * 20) == -1.0
-    assert c.conv_neg1_literal() == -1.0
-    assert c.conv_neg1_stor() == -1.0
-
-    assert c.conv((b"\x00" * 19) + b"\x01") == 1.0
-    assert c.conv((b"\x00" * 18) + b"\x01\x00") == 256.0
+""",
+    ]
+    for c in codes:
+        assert_compile_failed(lambda: get_contract(c), TypeMismatch)
 
 
 def test_convert_from_int256(get_contract_with_gas_estimation, assert_tx_failed):
diff --git a/tests/parser/functions/test_convert_to_int128.py b/tests/parser/functions/test_convert_to_int128.py
index 44729517..69bba664 100644
--- a/tests/parser/functions/test_convert_to_int128.py
+++ b/tests/parser/functions/test_convert_to_int128.py
@@ -1,5 +1,4 @@
 from vyper.exceptions import InvalidLiteral, OverflowException, TypeMismatch
-from vyper.utils import SizeLimits
 
 
 def test_convert_to_int128(get_contract_with_gas_estimation):
@@ -25,7 +24,7 @@ def bytes32_to_num() -> (int128, int128):
 @external
 def bytes_to_num() -> (int128, int128):
     self.c = b'a'
-    literal: int128 = convert('a', int128)
+    literal: int128 = convert(b'a', int128)
     storage: int128 = convert(self.c, int128)
     return literal, storage
 
@@ -59,7 +58,7 @@ def foo(bar: Bytes[5]) -> int128:
 
     test_success = """
 @external
-def foo(bar: Bytes[32]) -> int128:
+def foo(bar: Bytes[16]) -> int128:
     return convert(bar, int128)
     """
 
@@ -70,9 +69,7 @@ def foo(bar: Bytes[32]) -> int128:
     assert c.foo(b"\x00\x01") == 1
     assert c.foo(b"\x01\x00") == 256
     assert c.foo(b"\x01\x00\x00\x00\x01") == 4294967297
-    assert c.foo(b"\xff" * 32) == -1
-    assert_tx_failed(lambda: c.foo(b"\x80" + b"\xff" * 31))
-    assert_tx_failed(lambda: c.foo(b"\x01" * 33))
+    assert c.foo(b"\xff" * 16) == -1
 
     bytes_to_num_code = """
 astor: Bytes[10]
@@ -257,108 +254,13 @@ def foo() -> int128:
     )
 
 
-def test_convert_from_address(w3, get_contract):
+def test_convert_from_address(get_contract, assert_compile_failed):
     code = """
-stor: address
-
 @external
 def testCompiles():
     x: int128 = convert(msg.sender, int128)
-
-@external
-def conv_neg1_stor() -> int128:
-    self.stor = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF
-    return convert(self.stor, int128)
-
-@external
-def conv_neg1_literal() -> int128:
-    return convert(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF, int128)
-
-@external
-def conv_neg1_stor_alt() -> int128:
-    self.stor = 0x00000000fFFFffffffFfFfFFffFfFffFFFfFffff
-    return convert(self.stor, int128)
-
-@external
-def conv_neg1_literal_alt() -> int128:
-    return convert(0x00000000fFFFffffffFfFfFFffFfFffFFFfFffff, int128)
-
-@external
-def conv_min_stor() -> int128:
-    self.stor = 0x0000000080000000000000000000000000000000
-    return convert(self.stor, int128)
-
-@external
-def conv_min_literal() -> int128:
-    return convert(0x0000000080000000000000000000000000000000, int128)
-
-@external
-def conv_min_stor_alt() -> int128:
-    self.stor = 0x1234567880000000000000000000000000000000
-    return convert(self.stor, int128)
-
-@external
-def conv_min_literal_alt() -> int128:
-    return convert(0x1234567880000000000000000000000000000000, int128)
-
-@external
-def conv_zero_stor() -> int128:
-    self.stor = ZERO_ADDRESS
-    return convert(self.stor, int128)
-
-@external
-def conv_zero_literal() -> int128:
-    return convert(ZERO_ADDRESS, int128)
-
-@external
-def conv_zero_stor_alt() -> int128:
-    self.stor = 0xffFFfFFf00000000000000000000000000000000
-    return convert(self.stor, int128)
-
-@external
-def conv_zero_literal_alt() -> int128:
-    return convert(0xffFFfFFf00000000000000000000000000000000, int128)
-
-@external
-def conv_max_stor() -> int128:
-    self.stor = 0xFffffFff7FFFFFFfFffFffFfFFffFffFFfFfffFF
-    return convert(self.stor, int128)
-
-@external
-def conv_max_literal() -> int128:
-    return convert(0xFffffFff7FFFFFFfFffFffFfFFffFffFFfFfffFF, int128)
-
-@external
-def conv_max_stor_alt() -> int128:
-    self.stor = 0x000000007FfFFffffFFFFfffffffFffFfFffffFF
-    return convert(self.stor, int128)
-
-@external
-def conv_max_literal_alt() -> int128:
-    return convert(0x000000007FfFFffffFFFFfffffffFffFfFffffFF, int128)
     """
-
-    c = get_contract(code)
-
-    assert c.conv_neg1_stor() == -1
-    assert c.conv_neg1_literal() == -1
-    assert c.conv_neg1_stor_alt() == -1
-    assert c.conv_neg1_literal_alt() == -1
-
-    assert c.conv_min_stor() == SizeLimits.MIN_INT128
-    assert c.conv_min_literal() == SizeLimits.MIN_INT128
-    assert c.conv_min_stor_alt() == SizeLimits.MIN_INT128
-    assert c.conv_min_literal_alt() == SizeLimits.MIN_INT128
-
-    assert c.conv_zero_stor() == 0
-    assert c.conv_zero_literal() == 0
-    assert c.conv_zero_stor_alt() == 0
-    assert c.conv_zero_literal_alt() == 0
-
-    assert c.conv_max_stor() == SizeLimits.MAX_INT128
-    assert c.conv_max_literal() == SizeLimits.MAX_INT128
-    assert c.conv_max_stor_alt() == SizeLimits.MAX_INT128
-    assert c.conv_max_literal_alt() == SizeLimits.MAX_INT128
+    assert_compile_failed(lambda: get_contract(code), TypeMismatch)
 
 
 def test_convert_from_int256(get_contract_with_gas_estimation, assert_tx_failed):
diff --git a/tests/parser/functions/test_convert_to_int256.py b/tests/parser/functions/test_convert_to_int256.py
index 65acd596..ec43393f 100644
--- a/tests/parser/functions/test_convert_to_int256.py
+++ b/tests/parser/functions/test_convert_to_int256.py
@@ -24,7 +24,7 @@ def bytes32_to_num() -> (int256, int256):
 @external
 def bytes_to_num() -> (int256, int256):
     self.c = b'a'
-    literal: int256 = convert('a', int256)
+    literal: int256 = convert(b'a', int256)
     storage: int256 = convert(self.c, int256)
     return literal, storage
 
diff --git a/tests/parser/functions/test_convert_to_uint256.py b/tests/parser/functions/test_convert_to_uint256.py
index ea2066d3..ae8b8563 100644
--- a/tests/parser/functions/test_convert_to_uint256.py
+++ b/tests/parser/functions/test_convert_to_uint256.py
@@ -1,4 +1,4 @@
-from vyper.exceptions import InvalidLiteral
+from vyper.exceptions import InvalidLiteral, TypeMismatch
 
 # TODO: generalize all these test_convert_to_* test files and parametrize across all the types
 # NOTE: test_convert_to_uint8 tests are in the tests/parser/types/numbers/test_uint8.py file
@@ -16,7 +16,7 @@ def int128_to_uint256(inp: int128) -> (uint256, uint256, uint256):
     self.a = inp
     memory: uint256  = convert(inp, uint256)
     storage: uint256 = convert(self.a, uint256)
-    literal: uint256 = convert(1, uint256)
+    literal: uint256 = 1  # convert(1, uint256) fails because 1 is already uint256
     return  memory, storage, literal
 
 @external
@@ -61,7 +61,7 @@ def foo(bar: Bytes[33]) -> uint256:
     return convert(bar, uint256)
     """
 
-    assert_compile_failed(lambda: get_contract_with_gas_estimation(test_fail), InvalidLiteral)
+    assert_compile_failed(lambda: get_contract_with_gas_estimation(test_fail), TypeMismatch)
 
     test_fail = """
 @external
@@ -70,7 +70,7 @@ def foobar() -> uint256:
     return convert(barfoo, uint256)
     """
 
-    assert_compile_failed(lambda: get_contract_with_gas_estimation(test_fail), InvalidLiteral)
+    assert_compile_failed(lambda: get_contract_with_gas_estimation(test_fail), TypeMismatch)
 
 
 def test_convert_from_address(w3, get_contract):
diff --git a/tests/parser/functions/test_ecrecover.py b/tests/parser/functions/test_ecrecover.py
index a07552f5..e27cf681 100644
--- a/tests/parser/functions/test_ecrecover.py
+++ b/tests/parser/functions/test_ecrecover.py
@@ -10,9 +10,9 @@ def test_ecrecover(h: bytes32, v:uint256, r:uint256, s:uint256) -> address:
 @external
 def test_ecrecover2() -> address:
     return ecrecover(0x3535353535353535353535353535353535353535353535353535353535353535,
-                     convert(28, uint256),
-                     convert(63198938615202175987747926399054383453528475999185923188997970550032613358815, uint256),  # noqa: E501
-                     convert(6577251522710269046055727877571505144084475024240851440410274049870970796685, uint256))  # noqa: E501
+                     28,
+                     63198938615202175987747926399054383453528475999185923188997970550032613358815,
+                     6577251522710269046055727877571505144084475024240851440410274049870970796685)
     """
 
     c = get_contract_with_gas_estimation(ecrecover_test)
diff --git a/tests/parser/functions/test_unsafe_math.py b/tests/parser/functions/test_unsafe_math.py
index 98b69f39..ee47d300 100644
--- a/tests/parser/functions/test_unsafe_math.py
+++ b/tests/parser/functions/test_unsafe_math.py
@@ -4,11 +4,11 @@ import random
 
 import pytest
 
-from vyper.codegen.types.types import parse_integer_typeinfo
-from vyper.utils import BASE_TYPES, evm_div, int_bounds
+from vyper.codegen.types.types import INTEGER_TYPES, parse_integer_typeinfo
+from vyper.utils import evm_div, int_bounds
 
 # TODO something less janky
-integer_types = sorted([t for t in BASE_TYPES if "int" in t])
+integer_types = sorted(list(INTEGER_TYPES))
 
 
 def _as_signed(x, bits):
diff --git a/tests/parser/syntax/test_functions_call.py b/tests/parser/syntax/test_functions_call.py
index 8480b9e9..9aa836f2 100644
--- a/tests/parser/syntax/test_functions_call.py
+++ b/tests/parser/syntax/test_functions_call.py
@@ -16,9 +16,9 @@ def foo() -> uint256:
     (
         """
 @external
-def foo() -> uint256:
-    convert(2, uint256)
-    return convert(2, uint256)
+def foo(x: int256) -> uint256:
+    convert(x, uint256)
+    return convert(x, uint256)
 
     """,
         StructureException,
@@ -49,8 +49,8 @@ def test_functions_call_fail(bad_code, exc):
 valid_list = [
     """
 @external
-def foo() -> uint256:
-    return convert(2, uint256)
+def foo(x: int128) -> uint256:
+    return convert(x, uint256)
     """,
     """
 from vyper.interfaces import ERC20
diff --git a/tests/parser/types/numbers/test_uint256.py b/tests/parser/types/numbers/test_uint256.py
index 3e0428e2..69f0b75a 100644
--- a/tests/parser/types/numbers/test_uint256.py
+++ b/tests/parser/types/numbers/test_uint256.py
@@ -131,10 +131,10 @@ def test_modmul(get_contract_with_gas_estimation):
     modexper = """
 @external
 def exponential(base: uint256, exponent: uint256, modulus: uint256) -> uint256:
-    o: uint256 = convert(1, uint256)
+    o: uint256 = 1
     for i in range(256):
         o = uint256_mulmod(o, o, modulus)
-        if bitwise_and(exponent, shift(convert(1, uint256), 255 - i)) != convert(0, uint256):
+        if bitwise_and(exponent, shift(1, 255 - i)) != 0:
             o = uint256_mulmod(o, base, modulus)
     return o
     """