# beacon_chain
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Licensed and distributed under either of
#   * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
#   * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

{.used.}

# Test for spec functions and helpers outside of the EF test vectors - mainly
# helpers that extend or make the spec functions usable outside of the state
# transition functions

import
  unittest2,
  ../beacon_chain/spec/datatypes/phase0,
  ../beacon_chain/spec/[beaconstate, state_transition],
  ./testutil, ./testblockutil

suite "Beacon state" & preset():
  setup:
    let cfg = defaultRuntimeConfig

  test "Smoke test initialize_beacon_state_from_eth1" & preset():
    let state = newClone(initialize_beacon_state_from_eth1(
      cfg, ZERO_HASH, 0, makeInitialDeposits(SLOTS_PER_EPOCH, {}), {}))
    check: state.validators.lenu64 == SLOTS_PER_EPOCH

  test "process_slots":
    var
      state = (ref ForkedHashedBeaconState)(
        kind: BeaconStateFork.Phase0,
        phase0Data: initialize_hashed_beacon_state_from_eth1(
          defaultRuntimeConfig, ZERO_HASH, 0,
          makeInitialDeposits(SLOTS_PER_EPOCH, {}), {skipBlsValidation}))
      cache: StateCache
      info: ForkedEpochInfo
    check:
      process_slots(cfg, state[], Slot 1, cache, info, {}).isOk()
      process_slots(cfg, state[], Slot 1, cache, info, {}).isErr()
      process_slots(cfg, state[], Slot 1, cache, info, {slotProcessed}).isOk()

  test "latest_block_root":
    var
      state = (ref ForkedHashedBeaconState)(
        kind: BeaconStateFork.Phase0,
        phase0Data: initialize_hashed_beacon_state_from_eth1(
          defaultRuntimeConfig, ZERO_HASH, 0,
          makeInitialDeposits(SLOTS_PER_EPOCH, {}), {skipBlsValidation}))
      genBlock = get_initial_beacon_block(state[])
      cache: StateCache
      info: ForkedEpochInfo

    check: # Works for genesis block
      state[].phase0Data.latest_block_root == genBlock.root
      state[].phase0Data.latest_block_id == genBlock.toBlockId()

      process_slots(cfg, state[], Slot 1, cache, info, {}).isOk()
      state[].phase0Data.latest_block_root == genBlock.root

    let blck = addTestBlock(
      state[], cache, nextSlot = false, flags = {skipBlsValidation}).phase0Data

    check: # Works for random blocks
      state[].phase0Data.latest_block_root == blck.root
      process_slots(cfg, state[], Slot 2, cache, info, {}).isOk()
      state[].phase0Data.latest_block_root == blck.root

  test "get_beacon_proposer_index":
    var
      state = (ref ForkedHashedBeaconState)(
        kind: BeaconStateFork.Phase0,
        phase0Data: initialize_hashed_beacon_state_from_eth1(
          defaultRuntimeConfig, ZERO_HASH, 0,
          makeInitialDeposits(SLOTS_PER_EPOCH, {}), {skipBlsValidation}))
      cache: StateCache
      info: ForkedEpochInfo

    check:
      get_beacon_proposer_index(state[].phase0Data.data, cache, Slot 1).isSome()
      get_beacon_proposer_index(
        state[].phase0Data.data, cache, Epoch(1).start_slot()).isNone()
      get_beacon_proposer_index(
        state[].phase0Data.data, cache, Epoch(2).start_slot()).isNone()

    check:
      process_slots(cfg, state[], Epoch(1).start_slot(), cache, info, {}).isOk()
      get_beacon_proposer_index(state[].phase0Data.data, cache, Slot 1).isNone()
      get_beacon_proposer_index(
        state[].phase0Data.data, cache, Epoch(1).start_slot()).isSome()
      get_beacon_proposer_index(
        state[].phase0Data.data, cache, Epoch(2).start_slot()).isNone()

  test "dependent_root":
    var
      state = (ref ForkedHashedBeaconState)(
        kind: BeaconStateFork.Phase0,
        phase0Data: initialize_hashed_beacon_state_from_eth1(
          defaultRuntimeConfig, ZERO_HASH, 0,
          makeInitialDeposits(SLOTS_PER_EPOCH, {}), {skipBlsValidation}))
      genBlock = get_initial_beacon_block(state[])
      cache: StateCache
      info: ForkedEpochInfo

    check:
      state[].phase0Data.dependent_root(Epoch(0)) == genBlock.root

    while getStateField(state[], slot).epoch < Epoch(1):
      discard addTestBlock(state[], cache)

    check:
      state[].phase0Data.dependent_root(Epoch(1)) ==
        state[].phase0Data.data.get_block_root_at_slot(Epoch(1).start_slot - 1)
      state[].phase0Data.dependent_root(Epoch(0)) == genBlock.root

    while getStateField(state[], slot).epoch < Epoch(2):
      discard addTestBlock(state[], cache)

    check:
      state[].phase0Data.dependent_root(Epoch(2)) ==
        state[].phase0Data.data.get_block_root_at_slot(Epoch(2).start_slot - 1)
      state[].phase0Data.dependent_root(Epoch(1)) ==
        state[].phase0Data.data.get_block_root_at_slot(Epoch(1).start_slot - 1)
      state[].phase0Data.dependent_root(Epoch(0)) == genBlock.root

  test "merklizer state roundtrip":
    let
      dcs = DepositContractState()
      merkleizer = DepositsMerkleizer.init(dcs)

    check:
      dcs == merkleizer.toDepositContractState()

  test "can_advance_slots":
    var
      state = (ref ForkedHashedBeaconState)(
        kind: BeaconStateFork.Phase0,
        phase0Data: initialize_hashed_beacon_state_from_eth1(
          defaultRuntimeConfig, ZERO_HASH, 0,
          makeInitialDeposits(SLOTS_PER_EPOCH, {}), {skipBlsValidation}))
      genBlock = get_initial_beacon_block(state[])
      cache: StateCache
      info: ForkedEpochInfo

    check:
      state[].can_advance_slots(genBlock.root, Slot(0))
      state[].can_advance_slots(genBlock.root, Slot(0))
      state[].can_advance_slots(genBlock.root, Slot(0))

    let blck = addTestBlock(
      state[], cache, flags = {skipBlsValidation})

    check:
      not state[].can_advance_slots(genBlock.root, Slot(0))
      not state[].can_advance_slots(genBlock.root, Slot(0))
      not state[].can_advance_slots(genBlock.root, Slot(0))
      not state[].can_advance_slots(blck.root, Slot(0))
      state[].can_advance_slots(blck.root, Slot(1))
      state[].can_advance_slots(blck.root, Slot(2))