From a99977b77231922663e60686b68715ef3397b0df Mon Sep 17 00:00:00 2001
From: Zahary Karadjov <zahary@gmail.com>
Date: Mon, 18 May 2020 20:49:22 +0300
Subject: [PATCH] Progress towards #991; Enable the distinct ssz.List type

---
 beacon_chain/merkle_minimal.nim               |   6 +-
 beacon_chain/spec/beaconstate.nim             |  10 +-
 beacon_chain/spec/datatypes.nim               |  80 ++----------
 beacon_chain/spec/state_transition_block.nim  |  11 +-
 beacon_chain/spec/state_transition_epoch.nim  |  12 +-
 beacon_chain/ssz.nim                          | 119 ++++++------------
 beacon_chain/ssz/bytes_reader.nim             |  14 ++-
 beacon_chain/ssz/navigator.nim                |   2 +-
 beacon_chain/ssz/types.nim                    |  42 ++++++-
 beacon_chain/validator_duties.nim             |   2 +-
 tests/mocking/mock_deposits.nim               |   2 +-
 .../justification_finalization_helpers.nim    |   4 +-
 tests/test_ssz.nim                            |   4 +-
 vendor/nim-serialization                      |   2 +-
 14 files changed, 128 insertions(+), 182 deletions(-)

diff --git a/beacon_chain/merkle_minimal.nim b/beacon_chain/merkle_minimal.nim
index b4fa6eb3f..0c24314a2 100644
--- a/beacon_chain/merkle_minimal.nim
+++ b/beacon_chain/merkle_minimal.nim
@@ -115,17 +115,17 @@ proc testMerkleMinimal*(): bool =
 
   block: # SSZ Sanity checks vs Python impl
     block: # 3 leaves
-      let leaves = sszList(@[a, b, c], 3'i64)
+      let leaves = List[Eth2Digest, 3](@[a, b, c])
       let root = hash_tree_root(leaves)
       doAssert $root == "9ff412e827b7c9d40fc7df2725021fd579ab762581d1ff5c270316682868456e".toUpperAscii
 
     block: # 2^3 leaves
-      let leaves = sszList(@[a, b, c], int64(1 shl 3))
+      let leaves = List[Eth2Digest, int64(1 shl 3)](@[a, b, c])
       let root = hash_tree_root(leaves)
       doAssert $root == "5248085b588fab1dd1e03f3cd62201602b12e6560665935964f46e805977e8c5".toUpperAscii
 
     block: # 2^10 leaves
-      let leaves = sszList(@[a, b, c], int64(1 shl 10))
+      let leaves = List[Eth2Digest, int64(1 shl 10)](@[a, b, c])
       let root = hash_tree_root(leaves)
       doAssert $root == "9fb7d518368dc14e8cc588fb3fd2749beef9f493fef70ae34af5721543c67173".toUpperAscii
 
diff --git a/beacon_chain/spec/beaconstate.nim b/beacon_chain/spec/beaconstate.nim
index bb703da8a..d8755269d 100644
--- a/beacon_chain/spec/beaconstate.nim
+++ b/beacon_chain/spec/beaconstate.nim
@@ -265,8 +265,7 @@ proc initialize_beacon_state_from_eth1*(
       validator.activation_epoch = GENESIS_EPOCH
 
   # Set genesis validators root for domain separation and chain versioning
-  state.genesis_validators_root =
-    hash_tree_root(sszList(state.validators, VALIDATOR_REGISTRY_LIMIT))
+  state.genesis_validators_root = hash_tree_root(state.validators)
 
   state
 
@@ -402,8 +401,10 @@ proc is_valid_indexed_attestation*(
   #       https://github.com/status-im/nim-chronicles/issues/62
 
   # Verify indices are sorted and unique
+  # TODO: A simple loop can verify that the indicates are monotonically
+  #       increasing and non-repeating here!
   let indices = indexed_attestation.attesting_indices
-  if indices != sorted(toHashSet(indices).toSeq, system.cmp):
+  if indices.asSeq != sorted(toHashSet(indices.asSeq).toSeq, system.cmp):
     notice "indexed attestation: indices not sorted"
     return false
 
@@ -457,7 +458,8 @@ func get_indexed_attestation*(state: BeaconState, attestation: Attestation,
 
   IndexedAttestation(
     attesting_indices:
-      sorted(mapIt(attesting_indices.toSeq, it.uint64), system.cmp),
+      List[uint64, MAX_VALIDATORS_PER_COMMITTEE](
+        sorted(mapIt(attesting_indices.toSeq, it.uint64), system.cmp)),
     data: attestation.data,
     signature: attestation.signature
   )
diff --git a/beacon_chain/spec/datatypes.nim b/beacon_chain/spec/datatypes.nim
index 1fd4026a8..c62394526 100644
--- a/beacon_chain/spec/datatypes.nim
+++ b/beacon_chain/spec/datatypes.nim
@@ -25,7 +25,10 @@
 import
   macros, hashes, json, strutils, tables,
   stew/[byteutils, bitseqs], chronicles,
-  ../ssz/types, ./crypto, ./digest
+  ../ssz/types as sszTypes, ./crypto, ./digest
+
+export
+  sszTypes
 
 # TODO Data types:
 # Presently, we're reusing the data types from the serialization (uint64) in the
@@ -125,8 +128,6 @@ type
   Gwei* = uint64
   CommitteeIndex* = distinct uint64
 
-  BitList*[maxLen: static int] = distinct BitSeq
-
   # https://github.com/ethereum/eth2.0-specs/blob/v0.11.2/specs/phase0/beacon-chain.md#proposerslashing
   ProposerSlashing* = object
     signed_header_1*: SignedBeaconBlockHeader
@@ -273,7 +274,7 @@ type
 
     # Registry
     validators*: List[Validator, VALIDATOR_REGISTRY_LIMIT]
-    balances*: seq[uint64]
+    balances*: List[uint64, VALIDATOR_REGISTRY_LIMIT]
 
     # Randomness
     randao_mixes*: array[EPOCHS_PER_HISTORICAL_VECTOR, Eth2Digest]
@@ -405,43 +406,6 @@ type
       Table[Epoch, seq[ValidatorIndex]]
     committee_count_cache*: Table[Epoch, uint64]
 
-macro fieldMaxLen*(x: typed): untyped =
-  # TODO This macro is a temporary solution for the lack of a
-  # more proper way to specify the max length of the List[T; N]
-  # objects in the spec.
-  # May be replaced with `getCustomPragma` once we upgrade to
-  # Nim 0.20.2 or with a distinct List type, which would require
-  # more substantial refactorings in the spec code.
-  if x.kind != nnkDotExpr:
-    return newLit(0)
-
-  let size = case $x[1]
-             # Obsolete
-             of "pubkeys",
-                "compact_validators",
-                "aggregation_bits",
-                "custody_bits": int64(MAX_VALIDATORS_PER_COMMITTEE)
-             # IndexedAttestation
-             of "attesting_indices": MAX_VALIDATORS_PER_COMMITTEE
-             # BeaconBlockBody
-             of "proposer_slashings": MAX_PROPOSER_SLASHINGS
-             of "attester_slashings": MAX_ATTESTER_SLASHINGS
-             of "attestations": MAX_ATTESTATIONS
-             of "deposits": MAX_DEPOSITS
-             of "voluntary_exits": MAX_VOLUNTARY_EXITS
-             # BeaconState
-             of "historical_roots": HISTORICAL_ROOTS_LIMIT
-             of "eth1_data_votes":
-               EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH
-             of "validators": VALIDATOR_REGISTRY_LIMIT
-             of "balances": VALIDATOR_REGISTRY_LIMIT
-             of "previous_epoch_attestations",
-                "current_epoch_attestations": MAX_ATTESTATIONS *
-                                              SLOTS_PER_EPOCH
-             else: 0
-
-  newLit size
-
 func shortValidatorKey*(state: BeaconState, validatorIdx: int): string =
     ($state.validators[validatorIdx].pubkey)[0..7]
 
@@ -549,6 +513,14 @@ Json.useCustomSerialization(BitSeq):
   write:
     writer.writeValue "0x" & seq[byte](value).toHex
 
+template readValue*(reader: var JsonReader, value: var List) =
+  type T = type(value)
+  type E = ElemType(T)
+  value = T readValue(reader, seq[E])
+
+template writeValue*(writer: var JsonWriter, value: List) =
+  writeValue(writer, asSeq value)
+
 template readValue*(reader: var JsonReader, value: var BitList) =
   type T = type(value)
   value = T readValue(reader, BitSeq)
@@ -565,32 +537,6 @@ template newClone*[T: not ref](x: T): ref T =
 template newClone*[T](x: ref T not nil): ref T =
   newClone(x[])
 
-template init*(T: type BitList, len: int): auto = T init(BitSeq, len)
-template len*(x: BitList): auto = len(BitSeq(x))
-template bytes*(x: BitList): auto = bytes(BitSeq(x))
-template `[]`*(x: BitList, idx: auto): auto = BitSeq(x)[idx]
-template `[]=`*(x: var BitList, idx: auto, val: bool) = BitSeq(x)[idx] = val
-template `==`*(a, b: BitList): bool = BitSeq(a) == BitSeq(b)
-template setBit*(x: var BitList, idx: int) = setBit(BitSeq(x), idx)
-template clearBit*(x: var BitList, idx: int) = clearBit(BitSeq(x), idx)
-template overlaps*(a, b: BitList): bool = overlaps(BitSeq(a), BitSeq(b))
-template combine*(a: var BitList, b: BitList) = combine(BitSeq(a), BitSeq(b))
-template isSubsetOf*(a, b: BitList): bool = isSubsetOf(BitSeq(a), BitSeq(b))
-template `$`*(a: BitList): string = $(BitSeq(a))
-iterator items*(x: BitList): bool =
-  for i in 0 ..< x.len:
-    yield x[i]
-
-when useListType:
-  template len*[T; N](x: List[T, N]): auto = len(seq[T](x))
-  template `[]`*[T; N](x: List[T, N], idx: auto): auto = seq[T](x)[idx]
-  template `[]=`*[T; N](x: List[T, N], idx: auto, val: bool) = seq[T](x)[idx] = val
-  template `==`*[T; N](a, b: List[T, N]): bool = seq[T](a) == seq[T](b)
-  template asSeq*[T; N](x: List[T, N]): auto = seq[T](x)
-  template `&`*[T; N](a, b: List[T, N]): List[T, N] = seq[T](a) & seq[T](b)
-else:
-  template asSeq*[T; N](x: List[T, N]): auto = x
-
 func `$`*(v: ForkDigest | Version): string =
   toHex(array[4, byte](v))
 
diff --git a/beacon_chain/spec/state_transition_block.nim b/beacon_chain/spec/state_transition_block.nim
index 64bfec414..6e9460378 100644
--- a/beacon_chain/spec/state_transition_block.nim
+++ b/beacon_chain/spec/state_transition_block.nim
@@ -133,7 +133,7 @@ proc process_randao(
 # https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#eth1-data
 func process_eth1_data(state: var BeaconState, body: BeaconBlockBody) {.nbench.}=
   state.eth1_data_votes.add body.eth1_data
-  if state.eth1_data_votes.count(body.eth1_data) * 2 > SLOTS_PER_ETH1_VOTING_PERIOD.int:
+  if state.eth1_data_votes.asSeq.count(body.eth1_data) * 2 > SLOTS_PER_ETH1_VOTING_PERIOD.int:
     state.eth1_data = body.eth1_data
 
 # https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#is_slashable_validator
@@ -250,8 +250,8 @@ proc process_attester_slashing*(
     var slashed_any = false
 
     for index in sorted(toSeq(intersection(
-        toHashSet(attestation_1.attesting_indices),
-        toHashSet(attestation_2.attesting_indices)).items), system.cmp):
+        toHashSet(attestation_1.attesting_indices.asSeq),
+        toHashSet(attestation_2.attesting_indices.asSeq)).items), system.cmp):
       if is_slashable_validator(
           state.validators[index.int], get_current_epoch(state)):
         slash_validator(state, index.ValidatorIndex, stateCache)
@@ -478,9 +478,8 @@ proc makeBeaconBlock*(
       randao_reveal: randao_reveal,
       eth1_data: eth1data,
       graffiti: graffiti,
-      attestations: attestations,
-      deposits: deposits)
-  )
+      attestations: List[Attestation, MAX_ATTESTATIONS](attestations),
+      deposits: List[Deposit, MAX_DEPOSITS](deposits)))
 
   let tmpState = newClone(state)
   let ok = process_block(tmpState[], blck, {skipBlsValidation}, cache)
diff --git a/beacon_chain/spec/state_transition_epoch.nim b/beacon_chain/spec/state_transition_epoch.nim
index 74c717647..5817e3499 100644
--- a/beacon_chain/spec/state_transition_epoch.nim
+++ b/beacon_chain/spec/state_transition_epoch.nim
@@ -77,13 +77,13 @@ func get_total_active_balance*(state: BeaconState): Gwei =
     get_active_validator_indices(state, get_current_epoch(state)))
 
 # https://github.com/ethereum/eth2.0-specs/blob/v0.11.2/specs/phase0/beacon-chain.md#helper-functions-1
-func get_matching_source_attestations(state: BeaconState, epoch: Epoch):
-    seq[PendingAttestation] =
+func get_matching_source_attestations(state: BeaconState,
+                                      epoch: Epoch): seq[PendingAttestation] =
   doAssert epoch in [get_current_epoch(state), get_previous_epoch(state)]
   if epoch == get_current_epoch(state):
-    state.current_epoch_attestations
+    state.current_epoch_attestations.asSeq
   else:
-    state.previous_epoch_attestations
+    state.previous_epoch_attestations.asSeq
 
 func get_matching_target_attestations(state: BeaconState, epoch: Epoch):
     seq[PendingAttestation] =
@@ -384,7 +384,7 @@ func process_final_updates*(state: var BeaconState) {.nbench.}=
 
   # Reset eth1 data votes
   if next_epoch mod EPOCHS_PER_ETH1_VOTING_PERIOD == 0:
-    state.eth1_data_votes = @[]
+    state.eth1_data_votes = typeof(state.eth1_data_votes) @[]
 
   # Update effective balances with hysteresis
   for index, validator in state.validators:
@@ -420,7 +420,7 @@ func process_final_updates*(state: var BeaconState) {.nbench.}=
 
   # Rotate current/previous epoch attestations
   state.previous_epoch_attestations = state.current_epoch_attestations
-  state.current_epoch_attestations = @[]
+  state.current_epoch_attestations = typeof(state.current_epoch_attestations) @[]
 
 # https://github.com/ethereum/eth2.0-specs/blob/v0.11.2/specs/phase0/beacon-chain.md#epoch-processing
 proc process_epoch*(state: var BeaconState, updateFlags: UpdateFlags)
diff --git a/beacon_chain/ssz.nim b/beacon_chain/ssz.nim
index 1132fd567..1a48e0481 100644
--- a/beacon_chain/ssz.nim
+++ b/beacon_chain/ssz.nim
@@ -13,9 +13,9 @@
 #{.push raises: [Defect].}
 
 import
-  stew/shims/macros, options, algorithm, options, strformat,
+  options, algorithm, options, strformat, typetraits,
   stint, stew/[bitops2, bitseqs, objects, varints, ptrops],
-  stew/ranges/[ptr_arith, stackarrays],
+  stew/ranges/[ptr_arith, stackarrays], stew/shims/macros,
   faststreams/[inputs, outputs, buffers],
   serialization, serialization/testing/tracing,
   ./spec/[crypto, datatypes, digest],
@@ -44,14 +44,12 @@ type
   SszWriter* = object
     stream: OutputStream
 
-  BasicType = char|bool|SomeUnsignedInt|StUint|ValidatorIndex
+  BasicType = byte|char|bool|SomeUnsignedInt|StUint
 
   SszChunksMerkleizer = object
     combinedChunks: StackArray[Eth2Digest]
     totalChunks: uint64
 
-  TypeWithMaxLen*[T; maxLen: static int64] = distinct T
-
   SizePrefixed*[T] = distinct T
   SszMaxSizeExceeded* = object of SerializationError
 
@@ -89,20 +87,6 @@ method formatMsg*(err: ref SszSizeMismatchError, filename: string): string {.gcs
   except CatchableError:
     "SSZ size mismatch"
 
-when false:
-  # TODO: Nim can't handle yet this simpler definition. File an issue.
-  template valueOf[T; N](x: TypeWithMaxLen[T, N]): auto = T(x)
-else:
-  proc unwrapImpl[T; N: static int64](x: ptr TypeWithMaxLen[T, N]): ptr T =
-    cast[ptr T](x)
-
-  template valueOf(x: TypeWithMaxLen): auto =
-    let xaddr = unsafeAddr x
-    unwrapImpl(xaddr)[]
-
-template sszList*(x: seq|array, maxLen: static int64): auto =
-  TypeWithMaxLen[type(x), maxLen](x)
-
 template toSszType*(x: auto): auto =
   mixin toSszType
 
@@ -110,9 +94,8 @@ template toSszType*(x: auto): auto =
   elif x is Eth2Digest: x.data
   elif x is BlsCurveType: toRaw(x)
   elif x is BitSeq|BitList: ByteList(x)
-  elif x is TypeWithMaxLen: toSszType valueOf(x)
   elif x is ForkDigest|Version: array[4, byte](x)
-  elif useListType and x is List: seq[x.T](x)
+  elif x is List: asSeq(x)
   else: x
 
 proc writeFixedSized(s: var (OutputStream|WriteCursor), x: auto) {.raises: [Defect, IOError].} =
@@ -279,11 +262,6 @@ proc writeValue*[T](w: var SszWriter, x: SizePrefixed[T]) {.raises: [Defect, IOE
     buf.writeVarint length
     cursor.finalWrite buf.writtenBytes
 
-template fromSszBytes*[T; N](_: type TypeWithMaxLen[T, N],
-                             bytes: openarray[byte]): auto =
-  mixin fromSszBytes
-  fromSszBytes(T, bytes)
-
 proc readValue*[T](r: var SszReader, val: var T) {.raises: [Defect, MalformedSszError, SszSizeMismatchError, IOError].} =
   when isFixedSize(T):
     const minimalSize = fixedPortionSize(T)
@@ -435,18 +413,15 @@ template merkleizeFields(totalElements: int, body: untyped): Eth2Digest =
     addChunk(merkleizer, hash.data)
     trs "CHUNK ADDED"
 
-  template addField2(field) {.used.}=
-    const maxLen = fieldMaxLen(field)
-    when maxLen > 0:
-      type FieldType = type field
-      addField TypeWithMaxLen[FieldType, maxLen](field)
-    else:
-      addField field
-
   body
 
   getFinalHash(merkleizer)
 
+template writeBytesLE(chunk: var array[bytesPerChunk, byte], atParam: int,
+                      val: SomeUnsignedInt|StUint) =
+  let at = atParam
+  chunk[at ..< at + sizeof(val)] = toBytesLE(val)
+
 func chunkedHashTreeRootForBasicTypes[T](merkleizer: var SszChunksMerkleizer,
                                          arr: openarray[T]): Eth2Digest =
   static:
@@ -473,21 +448,17 @@ func chunkedHashTreeRootForBasicTypes[T](merkleizer: var SszChunksMerkleizer,
 
   else:
     static:
-      assert T is SomeUnsignedInt|StUInt|ValidatorIndex
+      assert T is SomeUnsignedInt|StUInt
       assert bytesPerChunk mod sizeof(Т) == 0
 
     const valuesPerChunk = bytesPerChunk div sizeof(Т)
 
-    template writeAt(chunk: var array[bytesPerChunk], at: int, val: SomeUnsignedInt|StUint) =
-      type ArrType = type toBytesLE(val)
-      (cast[ptr ArrType](addr chunk[at]))[] = toBytesLE(val)
-
     var writtenValues = 0
 
     var chunk: array[bytesPerChunk, byte]
     while writtenValues < arr.len - valuesPerChunk:
       for i in 0 ..< valuesPerChunk:
-        chunk.writeAt(i * sizeof(T), arr[writtenValues + i])
+        chunk.writeBytesLE(i * sizeof(T), arr[writtenValues + i])
       merkleizer.addChunk chunk
       inc writtenValues, valuesPerChunk
 
@@ -495,7 +466,7 @@ func chunkedHashTreeRootForBasicTypes[T](merkleizer: var SszChunksMerkleizer,
     if remainingValues > 0:
       var lastChunk: array[bytesPerChunk, byte]
       for i in 0 ..< remainingValues:
-        chunk.writeAt(i * sizeof(T), arr[writtenValues + i])
+        chunk.writeBytesLE(i * sizeof(T), arr[writtenValues + i])
       merkleizer.addChunk lastChunk
 
   getFinalHash(merkleizer)
@@ -552,9 +523,9 @@ func bitlistHashTreeRoot(merkleizer: var SszChunksMerkleizer, x: BitSeq): Eth2Di
   mixInLength contentsHash, x.len
 
 func maxChunksCount(T: type, maxLen: int64): int64 =
-  when T is BitList:
+  when T is BitSeq|BitList:
     (maxLen + bitsPerChunk - 1) div bitsPerChunk
-  elif T is seq|array:
+  elif T is array|List|seq|openarray:
     type E = ElemType(T)
     when E is BasicType:
       (maxLen * sizeof(E) + bytesPerChunk - 1) div bytesPerChunk
@@ -563,45 +534,37 @@ func maxChunksCount(T: type, maxLen: int64): int64 =
   else:
     unsupported T # This should never happen
 
-func hashTreeRootImpl[T](x: T): Eth2Digest =
+func hashTreeRootAux[T](x: T): Eth2Digest =
   when T is SignedBeaconBlock:
     unsupported T # Blocks are identified by htr(BeaconBlock) so we avoid these
   elif T is bool|char:
     result.data[0] = byte(x)
-  elif T is SomeUnsignedInt|StUint|ValidatorIndex:
+  elif T is SomeUnsignedInt|StUint:
     when cpuEndian == bigEndian:
       result.data[0..<sizeof(x)] = toBytesLE(x)
     else:
       copyMem(addr result.data[0], unsafeAddr x, sizeof x)
-  elif (when T is array: ElemType(T) is byte and
-      sizeof(T) == sizeof(Eth2Digest) else: false):
-    # TODO is this sizeof comparison guranteed? it's whole structure vs field
-    trs "ETH2DIGEST; IDENTITY MAPPING"
-    Eth2Digest(data: x)
   elif (when T is array: ElemType(T) is BasicType else: false):
-    trs "FIXED TYPE; USE CHUNK STREAM"
-    var markleizer = createMerkleizer(maxChunksCount(T, x.len))
-    chunkedHashTreeRootForBasicTypes(markleizer, x)
-  elif T is string or (when T is (seq|openarray): ElemType(T) is BasicType else: false):
-    trs "TYPE WITH LENGTH"
-    var markleizer = createMerkleizer(maxChunksCount(T, x.len))
-    mixInLength chunkedHashTreeRootForBasicTypes(markleizer, x), x.len
+    type E = ElemType(T)
+    when sizeof(T) <= sizeof(result.data):
+      when E is byte|char|bool or cpuEndian == littleEndian:
+        copyMem(addr result.data[0], unsafeAddr x, sizeof x)
+      else:
+        var pos = 0
+        for e in x:
+          writeBytesLE(result.data, pos, e)
+          pos += sizeof(E)
+    else:
+      trs "FIXED TYPE; USE CHUNK STREAM"
+      var markleizer = createMerkleizer(maxChunksCount(T, x.len))
+      chunkedHashTreeRootForBasicTypes(markleizer, x)
   elif T is array|object|tuple:
     trs "MERKLEIZING FIELDS"
     const totalFields = when T is array: len(x)
                         else: totalSerializedFields(T)
     merkleizeFields(totalFields):
       x.enumerateSubFields(f):
-        const maxLen = fieldMaxLen(f)
-        when maxLen > 0:
-          type FieldType = type f
-          addField TypeWithMaxLen[FieldType, maxLen](f)
-        else:
-          addField f
-  elif T is seq:
-    trs "SEQ WITH VAR SIZE"
-    let hash = merkleizeFields(x.len, for e in x: addField e)
-    mixInLength hash, x.len
+        addField f
   #elif isCaseObject(T):
   #  # TODO implement this
   else:
@@ -610,28 +573,26 @@ func hashTreeRootImpl[T](x: T): Eth2Digest =
 func hash_tree_root*(x: auto): Eth2Digest {.raises: [Defect], nbench.} =
   trs "STARTING HASH TREE ROOT FOR TYPE ", name(type(x))
   mixin toSszType
-  when x is TypeWithMaxLen:
-    const maxLen = x.maxLen
-    type T = type valueOf(x)
+  when x is List|BitList:
+    const maxLen = static(x.maxLen)
+    type T = type(x)
     const limit = maxChunksCount(T, maxLen)
     var merkleizer = createMerkleizer(limit)
 
-    when T is BitList:
-      result = merkleizer.bitlistHashTreeRoot(BitSeq valueOf(x))
-    elif T is seq:
+    when x is BitList:
+      result = merkleizer.bitlistHashTreeRoot(BitSeq x)
+    else:
       type E = ElemType(T)
       let contentsHash = when E is BasicType:
-        chunkedHashTreeRootForBasicTypes(merkleizer, valueOf(x))
+        chunkedHashTreeRootForBasicTypes(merkleizer, asSeq x)
       else:
-        for elem in valueOf(x):
+        for elem in x:
           let elemHash = hash_tree_root(elem)
           merkleizer.addChunk(elemHash.data)
         merkleizer.getFinalHash()
-      result = mixInLength(contentsHash, valueOf(x).len)
-    else:
-      unsupported T # This should never happen
+      result = mixInLength(contentsHash, x.len)
   else:
-    result = hashTreeRootImpl toSszType(x)
+    result = hashTreeRootAux toSszType(x)
 
   trs "HASH TREE ROOT FOR ", name(type x), " = ", "0x", $result
 
diff --git a/beacon_chain/ssz/bytes_reader.nim b/beacon_chain/ssz/bytes_reader.nim
index 1dbf48684..a9d7e75cf 100644
--- a/beacon_chain/ssz/bytes_reader.nim
+++ b/beacon_chain/ssz/bytes_reader.nim
@@ -97,7 +97,7 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
   template readOffset(n: int): int {.used.}=
     int fromSszBytes(uint32, input.toOpenArray(n, n + offsetSize - 1))
 
-  when useListType and result is List:
+  when result is List:
     type ElemType = type result[0]
     result = T readSszValue(input, seq[ElemType])
 
@@ -199,8 +199,16 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
           SszType)
         trs "READING COMPLETE ", fieldName
 
-      elif useListType and FieldType is List:
-        field = readSszValue(
+      elif FieldType is List:
+        # TODO
+        # The `typeof(field)` coercion below is required to deal with a Nim
+        # bug. For some reason, Nim gets confused about the type of the list
+        # returned from the `readSszValue` function. This could be a generics
+        # caching issue caused by the use of distinct types. Such an issue
+        # would be very scary in general, but in this particular situation
+        # it shouldn't matter, because the different flavours of `List[T, N]`
+        # won't produce different serializations.
+        field = typeof(field) readSszValue(
           input.toOpenArray(startOffset, endOffset - 1),
           FieldType)
 
diff --git a/beacon_chain/ssz/navigator.nim b/beacon_chain/ssz/navigator.nim
index 5d8a8ea32..46fdbb469 100644
--- a/beacon_chain/ssz/navigator.nim
+++ b/beacon_chain/ssz/navigator.nim
@@ -120,7 +120,7 @@ template `[]`*[R, T](n: SszNavigator[array[R, T]], idx: int): SszNavigator[T] =
 func `[]`*[T](n: SszNavigator[T]): T {.raisesssz.} =
   mixin toSszType, fromSszBytes
   type SszRepr = type toSszType(declval T)
-  when type(SszRepr) is type(T):
+  when type(SszRepr) is type(T) or T is List:
     readSszValue(toOpenArray(n.m), T)
   else:
     fromSszBytes(T, toOpenArray(n.m))
diff --git a/beacon_chain/ssz/types.nim b/beacon_chain/ssz/types.nim
index 948ceae66..ab19259f6 100644
--- a/beacon_chain/ssz/types.nim
+++ b/beacon_chain/ssz/types.nim
@@ -1,12 +1,11 @@
 {.push raises: [Defect].}
 
 import
-  tables, options,
+  tables, options, typetraits,
   stew/shims/macros, stew/[objects, bitseqs],
   serialization/[object_serialization, errors]
 
 const
-  useListType* = false
   offsetSize* = 4
 
 type
@@ -63,10 +62,41 @@ type
     of Field:
       discard
 
-when useListType:
-  type List*[T; maxLen: static int64] = distinct seq[T]
-else:
-  type List*[T; maxLen: static int64] = seq[T]
+  List*[T; maxLen: static int64] = distinct seq[T]
+  BitList*[maxLen: static int] = distinct BitSeq
+
+template add*(x: List, val: x.T) = add(distinctBase x, val)
+template len*(x: List): auto = len(distinctBase x)
+template low*(x: List): auto = low(distinctBase x)
+template high*(x: List): auto = high(distinctBase x)
+template `[]`*(x: List, idx: auto): auto = distinctBase(x)[idx]
+template `[]=`*[T; N](x: List[T, N], idx: auto, val: T) = seq[T](x)[idx] = val
+template `==`*(a, b: List): bool = distinctBase(a) == distinctBase(b)
+template asSeq*(x: List): auto = distinctBase x
+template `&`*[T; N](a, b: List[T, N]): List[T, N] = List[T, N](seq[T](a) & seq[T](b))
+template `$`*(x: List): auto = $(distinctBase x)
+
+template items* (x: List): untyped = items(distinctBase x)
+template pairs* (x: List): untyped = pairs(distinctBase x)
+template mitems*(x: List): untyped = mitems(distinctBase x)
+template mpairs*(x: List): untyped = mpairs(distinctBase x)
+
+template init*(T: type BitList, len: int): auto = T init(BitSeq, len)
+template len*(x: BitList): auto = len(BitSeq(x))
+template bytes*(x: BitList): auto = bytes(BitSeq(x))
+template `[]`*(x: BitList, idx: auto): auto = BitSeq(x)[idx]
+template `[]=`*(x: var BitList, idx: auto, val: bool) = BitSeq(x)[idx] = val
+template `==`*(a, b: BitList): bool = BitSeq(a) == BitSeq(b)
+template setBit*(x: var BitList, idx: int) = setBit(BitSeq(x), idx)
+template clearBit*(x: var BitList, idx: int) = clearBit(BitSeq(x), idx)
+template overlaps*(a, b: BitList): bool = overlaps(BitSeq(a), BitSeq(b))
+template combine*(a: var BitList, b: BitList) = combine(BitSeq(a), BitSeq(b))
+template isSubsetOf*(a, b: BitList): bool = isSubsetOf(BitSeq(a), BitSeq(b))
+template `$`*(a: BitList): string = $(BitSeq(a))
+
+iterator items*(x: BitList): bool =
+  for i in 0 ..< x.len:
+    yield x[i]
 
 macro unsupported*(T: typed): untyped =
   # TODO: {.fatal.} breaks compilation even in `compiles()` context,
diff --git a/beacon_chain/validator_duties.nim b/beacon_chain/validator_duties.nim
index f806c075a..d75a735ab 100644
--- a/beacon_chain/validator_duties.nim
+++ b/beacon_chain/validator_duties.nim
@@ -54,7 +54,7 @@ proc addLocalValidator*(node: BeaconNode,
                        privKey: ValidatorPrivKey) =
   let pubKey = privKey.toPubKey()
 
-  let idx = state.validators.findIt(it.pubKey == pubKey)
+  let idx = state.validators.asSeq.findIt(it.pubKey == pubKey)
   if idx == -1:
     # We allow adding a validator even if its key is not in the state registry:
     # it might be that the deposit for this validator has not yet been processed
diff --git a/tests/mocking/mock_deposits.nim b/tests/mocking/mock_deposits.nim
index 1ab69abb9..40f5952d0 100644
--- a/tests/mocking/mock_deposits.nim
+++ b/tests/mocking/mock_deposits.nim
@@ -210,5 +210,5 @@ proc mockUpdateStateForNewDeposit*(
   #       but confirmed by running it
   state.eth1_deposit_index = 0
   state.eth1_data.deposit_root =
-     hash_tree_root(sszList(@[result.data], 2'i64^DEPOSIT_CONTRACT_TREE_DEPTH))
+     hash_tree_root(List[DepositData, 2'i64^DEPOSIT_CONTRACT_TREE_DEPTH](@[result.data]))
   state.eth1_data.deposit_count = 1
diff --git a/tests/spec_epoch_processing/justification_finalization_helpers.nim b/tests/spec_epoch_processing/justification_finalization_helpers.nim
index 83d91631f..b0a7e04ae 100644
--- a/tests/spec_epoch_processing/justification_finalization_helpers.nim
+++ b/tests/spec_epoch_processing/justification_finalization_helpers.nim
@@ -27,9 +27,9 @@ proc addMockAttestations*(
   # Alias the attestations container
   var attestations: ptr seq[PendingAttestation]
   if state.get_current_epoch() == epoch:
-    attestations = state.current_epoch_attestations.addr
+    attestations = state.current_epoch_attestations.asSeq.addr
   elif state.get_previous_epoch() == epoch:
-    attestations = state.previous_epoch_attestations.addr
+    attestations = state.previous_epoch_attestations.asSeq.addr
   else:
     raise newException(ValueError, &"Cannot include attestations from epoch {state.get_current_epoch()} in epoch {epoch}")
 
diff --git a/tests/test_ssz.nim b/tests/test_ssz.nim
index 1d390e5ca..384e037bb 100644
--- a/tests/test_ssz.nim
+++ b/tests/test_ssz.nim
@@ -93,11 +93,11 @@ suiteReport "SSZ navigator":
     let b = [byte 0x04, 0x05, 0x06].toDigest
     let c = [byte 0x07, 0x08, 0x09].toDigest
 
-    let leaves = sszList(@[a, b, c], int64(1 shl 3))
+    let leaves = List[Eth2Digest, int64(1 shl 3)](@[a, b, c])
     let root = hash_tree_root(leaves)
     check $root == "5248085B588FAB1DD1E03F3CD62201602B12E6560665935964F46E805977E8C5"
 
-    let leaves2 = sszList(@[a, b, c], int64(1 shl 10))
+    let leaves2 = List[Eth2Digest, int64(1 shl 10)](@[a, b, c])
     let root2 = hash_tree_root(leaves2)
     check $root2 == "9FB7D518368DC14E8CC588FB3FD2749BEEF9F493FEF70AE34AF5721543C67173"
 
diff --git a/vendor/nim-serialization b/vendor/nim-serialization
index 7ff764ca1..b61fcb51a 160000
--- a/vendor/nim-serialization
+++ b/vendor/nim-serialization
@@ -1 +1 @@
-Subproject commit 7ff764ca1f921ee709f05219aa740ab6c42872fc
+Subproject commit b61fcb51ad817d1d7bba6d6cd255dec3eb2f6987