use super::*;

#[derive(Debug, PartialEq)]
pub enum MintError {
  Cap(u128),
  End(u64),
  Start(u64),
  Unmintable,
}

impl Display for MintError {
  fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    match self {
      MintError::Cap(cap) => write!(f, "limited to {cap} mints"),
      MintError::End(end) => write!(f, "mint ended on block {end}"),
      MintError::Start(start) => write!(f, "mint starts on block {start}"),
      MintError::Unmintable => write!(f, "not mintable"),
    }
  }
}

#[cfg(test)]
mod tests {
  use {super::*, crate::index::testing::Context};

  const RUNE: u128 = 99246114928149462;

  #[test]
  fn index_starts_with_no_runes() {
    let context = Context::builder().arg("--index-runes").build();
    context.assert_runes([], []);
  }

  #[test]
  fn default_index_does_not_index_runes() {
    let context = Context::builder().build();

    context.mine_blocks(1);

    context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes([], []);
  }

  #[test]
  fn empty_runestone_does_not_create_rune() {
    let context = Context::builder().arg("--index-runes").build();

    context.mine_blocks(1);

    context.etch(Default::default(), 1);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(1, 0, 0, Witness::new())],
      op_return: Some(Runestone::default().encipher()),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes([], []);
  }

  #[test]
  fn etching_with_no_edicts_creates_rune() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          ..default()
        },
      )],
      [],
    );
  }

  #[test]
  fn etching_with_edict_creates_rune() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])],
    );
  }

  #[test]
  fn runes_must_be_greater_than_or_equal_to_minimum_for_height() {
    let minimum = Rune::minimum_at_height(
      Chain::Regtest.network(),
      Height((Runestone::COMMIT_CONFIRMATIONS + 2).into()),
    )
    .0;

    {
      let context = Context::builder()
        .chain(Chain::Regtest)
        .arg("--index-runes")
        .build();

      context.etch(
        Runestone {
          edicts: vec![Edict {
            id: RuneId::default(),
            amount: u128::MAX,
            output: 0,
          }],
          etching: Some(Etching {
            rune: Some(Rune(minimum - 1)),
            premine: Some(u128::MAX),
            ..default()
          }),
          ..default()
        },
        1,
      );

      context.assert_runes([], []);
    }

    {
      let context = Context::builder()
        .chain(Chain::Regtest)
        .arg("--index-runes")
        .build();

      let (txid, id) = context.etch(
        Runestone {
          edicts: vec![Edict {
            id: RuneId::default(),
            amount: u128::MAX,
            output: 0,
          }],
          etching: Some(Etching {
            rune: Some(Rune(minimum)),
            premine: Some(u128::MAX),
            ..default()
          }),
          ..default()
        },
        1,
      );

      context.assert_runes(
        [(
          id,
          RuneEntry {
            block: id.block,
            etching: txid,
            spaced_rune: SpacedRune {
              rune: Rune(minimum),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id.block,
            ..default()
          },
        )],
        [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])],
      );
    }
  }

  #[test]
  fn etching_cannot_specify_reserved_rune() {
    {
      let context = Context::builder().arg("--index-runes").build();

      context.etch(
        Runestone {
          edicts: vec![Edict {
            id: RuneId::default(),
            amount: u128::MAX,
            output: 0,
          }],
          etching: Some(Etching {
            rune: Some(Rune::reserved(0, 0)),
            ..default()
          }),
          ..default()
        },
        1,
      );

      context.assert_runes([], []);
    }

    {
      let context = Context::builder().arg("--index-runes").build();

      let (txid, id) = context.etch(
        Runestone {
          edicts: vec![Edict {
            id: RuneId::default(),
            amount: u128::MAX,
            output: 0,
          }],
          etching: Some(Etching {
            rune: Some(Rune(Rune::reserved(0, 0).n() - 1)),
            premine: Some(u128::MAX),
            ..default()
          }),
          ..default()
        },
        1,
      );

      context.assert_runes(
        [(
          id,
          RuneEntry {
            block: id.block,
            etching: txid,
            spaced_rune: SpacedRune {
              rune: Rune(Rune::reserved(0, 0).n() - 1),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id.block,
            ..default()
          },
        )],
        [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])],
      );
    }
  }

  #[test]
  fn reserved_runes_may_be_etched() {
    let context = Context::builder().arg("--index-runes").build();

    context.mine_blocks(1);

    let txid0 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(1, 0, 0, Witness::new())],
      outputs: 2,
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id: RuneId::default(),
            amount: u128::MAX,
            output: 0,
          }],
          etching: Some(Etching {
            rune: None,
            premine: Some(u128::MAX),
            ..default()
          }),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    let id0 = RuneId { block: 2, tx: 1 };

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id0,
        RuneEntry {
          block: id0.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune::reserved(id0.block, id0.tx),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: 2,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id0, u128::MAX)],
      )],
    );

    context.mine_blocks(1);

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id: RuneId::default(),
            amount: u128::MAX,
            output: 0,
          }],
          etching: Some(Etching {
            premine: Some(u128::MAX),
            rune: None,
            ..default()
          }),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    let id1 = RuneId { block: 4, tx: 1 };

    context.assert_runes(
      [
        (
          id0,
          RuneEntry {
            block: id0.block,
            etching: txid0,
            spaced_rune: SpacedRune {
              rune: Rune::reserved(id0.block, id0.tx),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: 2,
            ..default()
          },
        ),
        (
          id1,
          RuneEntry {
            block: id1.block,
            etching: txid1,
            spaced_rune: SpacedRune {
              rune: Rune::reserved(id1.block, id0.tx),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: 4,
            number: 1,
            ..default()
          },
        ),
      ],
      [
        (
          OutPoint {
            txid: txid0,
            vout: 0,
          },
          vec![(id0, u128::MAX)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id1, u128::MAX)],
        ),
      ],
    );
  }

  #[test]
  fn etching_with_non_zero_divisibility_and_rune() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          divisibility: Some(1),
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          etching: txid,
          divisibility: 1,
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])],
    );
  }

  #[test]
  fn allocations_over_max_supply_are_ignored() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![
          Edict {
            id: RuneId::default(),
            amount: u128::MAX,
            output: 0,
          },
          Edict {
            id: RuneId::default(),
            amount: u128::MAX,
            output: 0,
          },
        ],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])],
    );
  }

  #[test]
  fn allocations_partially_over_max_supply_are_honored() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![
          Edict {
            id: RuneId::default(),
            amount: u128::MAX / 2,
            output: 0,
          },
          Edict {
            id: RuneId::default(),
            amount: u128::MAX,
            output: 0,
          },
        ],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          symbol: None,
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])],
    );
  }

  #[test]
  fn etching_may_allocate_less_than_max_supply() {
    let context = Context::builder().arg("--index-runes").build();

    context.mine_blocks(1);

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: 100,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(100),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: 100,
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, 100)])],
    );
  }

  #[test]
  fn etching_may_allocate_to_multiple_outputs() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![
          Edict {
            id: RuneId::default(),
            amount: 100,
            output: 0,
          },
          Edict {
            id: RuneId::default(),
            amount: 100,
            output: 1,
          },
        ],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(200),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          burned: 100,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: 200,
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, 100)])],
    );
  }

  #[test]
  fn allocations_to_invalid_outputs_produce_cenotaph() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![
          Edict {
            id: RuneId::default(),
            amount: 100,
            output: 0,
          },
          Edict {
            id: RuneId::default(),
            amount: 100,
            output: 3,
          },
        ],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: 0,
          timestamp: id.block,
          ..default()
        },
      )],
      [],
    );
  }

  #[test]
  fn input_runes_may_be_allocated() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: u128::MAX,
            output: 0,
          }],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );
  }

  #[test]
  fn etched_rune_is_allocated_with_zero_supply_for_cenotaph() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          ..default()
        }),
        pointer: Some(10),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          ..default()
        },
      )],
      [],
    );
  }

  #[test]
  fn etched_rune_parameters_are_unset_for_cenotaph() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          premine: Some(u128::MAX),
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            cap: Some(1),
            amount: Some(1),
            offset: (Some(1), Some(1)),
            height: (None, None),
          }),
          divisibility: Some(1),
          symbol: Some('$'),
          spacers: Some(1),
          turbo: true,
        }),
        pointer: Some(10),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          burned: 0,
          divisibility: 0,
          etching: txid0,
          terms: None,
          mints: 0,
          number: 0,
          premine: 0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          symbol: None,
          timestamp: id.block,
          turbo: false,
        },
      )],
      [],
    );
  }

  #[test]
  fn reserved_runes_are_not_allocated_in_cenotaph() {
    let context = Context::builder().arg("--index-runes").build();

    context.mine_blocks(1);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(1, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id: RuneId::default(),
            amount: u128::MAX,
            output: 0,
          }],
          etching: Some(Etching::default()),
          pointer: Some(10),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes([], []);
  }

  #[test]
  fn input_runes_are_burned_if_an_unrecognized_even_tag_is_encountered() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      op_return: Some(
        Runestone {
          pointer: Some(10),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          burned: u128::MAX,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [],
    );
  }

  #[test]
  fn unallocated_runes_are_assigned_to_first_non_op_return_output() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      op_return: Some(Runestone::default().encipher()),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );
  }

  #[test]
  fn unallocated_runes_are_burned_if_no_non_op_return_output_is_present() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      op_return: Some(Runestone::default().encipher()),
      outputs: 0,
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          burned: u128::MAX,
          ..default()
        },
      )],
      [],
    );
  }

  #[test]
  fn unallocated_runes_are_assigned_to_default_output() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      outputs: 2,
      op_return: Some(
        Runestone {
          pointer: Some(1),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 1,
        },
        vec![(id, u128::MAX)],
      )],
    );
  }

  #[test]
  fn unallocated_runes_are_burned_if_default_output_is_op_return() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      outputs: 2,
      op_return: Some(
        Runestone {
          pointer: Some(2),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          burned: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [],
    );
  }

  #[test]
  fn unallocated_runes_in_transactions_with_no_runestone_are_assigned_to_first_non_op_return_output()
   {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      op_return: None,
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );
  }

  #[test]
  fn duplicate_runes_are_forbidden() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])],
    );

    context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])],
    );
  }

  #[test]
  fn output_may_hold_multiple_runes() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id0) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id0,
        RuneEntry {
          block: id0.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id0.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id0, u128::MAX)],
      )],
    );

    let (txid1, id1) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE + 1)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [
        (
          id0,
          RuneEntry {
            block: id0.block,
            etching: txid0,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id0.block,
            ..default()
          },
        ),
        (
          id1,
          RuneEntry {
            block: id1.block,
            etching: txid1,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE + 1),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id1.block,
            number: 1,
            ..default()
          },
        ),
      ],
      [
        (
          OutPoint {
            txid: txid0,
            vout: 0,
          },
          vec![(id0, u128::MAX)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id1, u128::MAX)],
        ),
      ],
    );

    let txid2 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[
        (id0.block.try_into().unwrap(), 1, 0, Witness::new()),
        (id1.block.try_into().unwrap(), 1, 0, Witness::new()),
      ],
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [
        (
          id0,
          RuneEntry {
            block: id0.block,
            etching: txid0,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id0.block,
            ..default()
          },
        ),
        (
          id1,
          RuneEntry {
            block: id1.block,
            etching: txid1,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE + 1),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id1.block,
            number: 1,
            ..default()
          },
        ),
      ],
      [(
        OutPoint {
          txid: txid2,
          vout: 0,
        },
        vec![(id0, u128::MAX), (id1, u128::MAX)],
      )],
    );
  }

  #[test]
  fn multiple_input_runes_on_the_same_input_may_be_allocated() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id0) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id0,
        RuneEntry {
          block: id0.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id0.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id0, u128::MAX)],
      )],
    );

    let (txid1, id1) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE + 1)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [
        (
          id0,
          RuneEntry {
            block: id0.block,
            etching: txid0,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id0.block,
            ..default()
          },
        ),
        (
          id1,
          RuneEntry {
            block: id1.block,
            etching: txid1,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE + 1),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id1.block,
            number: 1,
            ..default()
          },
        ),
      ],
      [
        (
          OutPoint {
            txid: txid0,
            vout: 0,
          },
          vec![(id0, u128::MAX)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id1, u128::MAX)],
        ),
      ],
    );

    let txid2 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[
        (id0.block.try_into().unwrap(), 1, 0, Witness::new()),
        (id1.block.try_into().unwrap(), 1, 0, Witness::new()),
      ],
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [
        (
          id0,
          RuneEntry {
            block: id0.block,
            etching: txid0,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id0.block,
            ..default()
          },
        ),
        (
          id1,
          RuneEntry {
            block: id1.block,
            etching: txid1,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE + 1),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id1.block,
            number: 1,
            ..default()
          },
        ),
      ],
      [(
        OutPoint {
          txid: txid2,
          vout: 0,
        },
        vec![(id0, u128::MAX), (id1, u128::MAX)],
      )],
    );

    let txid3 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[((id1.block + 1).try_into().unwrap(), 1, 0, Witness::new())],
      outputs: 2,
      op_return: Some(
        Runestone {
          edicts: vec![
            Edict {
              id: id0,
              amount: u128::MAX / 2,
              output: 1,
            },
            Edict {
              id: id1,
              amount: u128::MAX / 2,
              output: 1,
            },
          ],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [
        (
          id0,
          RuneEntry {
            block: id0.block,
            etching: txid0,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id0.block,
            ..default()
          },
        ),
        (
          id1,
          RuneEntry {
            block: id1.block,
            etching: txid1,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE + 1),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id1.block,
            number: 1,
            ..default()
          },
        ),
      ],
      [
        (
          OutPoint {
            txid: txid3,
            vout: 0,
          },
          vec![(id0, u128::MAX / 2 + 1), (id1, u128::MAX / 2 + 1)],
        ),
        (
          OutPoint {
            txid: txid3,
            vout: 1,
          },
          vec![(id0, u128::MAX / 2), (id1, u128::MAX / 2)],
        ),
      ],
    );
  }

  #[test]
  fn multiple_input_runes_on_different_inputs_may_be_allocated() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id0) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id0,
        RuneEntry {
          block: id0.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id0.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id0, u128::MAX)],
      )],
    );

    let (txid1, id1) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE + 1)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [
        (
          id0,
          RuneEntry {
            block: id0.block,
            etching: txid0,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id0.block,
            ..default()
          },
        ),
        (
          id1,
          RuneEntry {
            block: id1.block,
            etching: txid1,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE + 1),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id1.block,
            number: 1,
            ..default()
          },
        ),
      ],
      [
        (
          OutPoint {
            txid: txid0,
            vout: 0,
          },
          vec![(id0, u128::MAX)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id1, u128::MAX)],
        ),
      ],
    );

    let txid2 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[
        (id0.block.try_into().unwrap(), 1, 0, Witness::new()),
        (id1.block.try_into().unwrap(), 1, 0, Witness::new()),
      ],
      op_return: Some(
        Runestone {
          edicts: vec![
            Edict {
              id: id0,
              amount: u128::MAX,
              output: 0,
            },
            Edict {
              id: id1,
              amount: u128::MAX,
              output: 0,
            },
          ],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [
        (
          id0,
          RuneEntry {
            block: id0.block,
            etching: txid0,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id0.block,
            ..default()
          },
        ),
        (
          id1,
          RuneEntry {
            block: id1.block,
            etching: txid1,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE + 1),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id1.block,
            number: 1,
            ..default()
          },
        ),
      ],
      [(
        OutPoint {
          txid: txid2,
          vout: 0,
        },
        vec![(id0, u128::MAX), (id1, u128::MAX)],
      )],
    );
  }

  #[test]
  fn unallocated_runes_are_assigned_to_first_non_op_return_output_when_op_return_is_not_last_output()
   {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      op_return: Some(
        script::Builder::new()
          .push_opcode(opcodes::all::OP_RETURN)
          .into_script(),
      ),
      op_return_index: Some(0),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 1 }, vec![(id, u128::MAX)])],
    );
  }

  #[test]
  fn multiple_runes_may_be_etched_in_one_block() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id0) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    let (txid1, id1) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE + 1)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [
        (
          id0,
          RuneEntry {
            block: id0.block,
            etching: txid0,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id0.block,
            ..default()
          },
        ),
        (
          id1,
          RuneEntry {
            block: id1.block,
            etching: txid1,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE + 1),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id1.block,
            number: 1,
            ..default()
          },
        ),
      ],
      [
        (
          OutPoint {
            txid: txid0,
            vout: 0,
          },
          vec![(id0, u128::MAX)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id1, u128::MAX)],
        ),
      ],
    );
  }

  #[test]
  fn edicts_with_id_zero_are_skipped() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![
            Edict {
              id: RuneId::default(),
              amount: 100,
              output: 0,
            },
            Edict {
              id,
              amount: u128::MAX,
              output: 0,
            },
          ],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );
  }

  #[test]
  fn edicts_which_refer_to_input_rune_with_no_balance_are_skipped() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id0) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id0,
        RuneEntry {
          block: id0.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id0.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id0, u128::MAX)],
      )],
    );

    let (txid1, id1) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE + 1)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [
        (
          id0,
          RuneEntry {
            block: id0.block,
            etching: txid0,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id0.block,
            ..default()
          },
        ),
        (
          id1,
          RuneEntry {
            block: id1.block,
            etching: txid1,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE + 1),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id1.block,
            number: 1,
            ..default()
          },
        ),
      ],
      [
        (
          OutPoint {
            txid: txid0,
            vout: 0,
          },
          vec![(id0, u128::MAX)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id1, u128::MAX)],
        ),
      ],
    );

    let txid2 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id0.block.try_into().unwrap(), 1, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![
            Edict {
              id: id0,
              amount: u128::MAX,
              output: 0,
            },
            Edict {
              id: id1,
              amount: u128::MAX,
              output: 0,
            },
          ],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [
        (
          id0,
          RuneEntry {
            block: id0.block,
            etching: txid0,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id0.block,
            ..default()
          },
        ),
        (
          id1,
          RuneEntry {
            block: id1.block,
            etching: txid1,
            spaced_rune: SpacedRune {
              rune: Rune(RUNE + 1),
              spacers: 0,
            },
            premine: u128::MAX,
            timestamp: id1.block,
            number: 1,
            ..default()
          },
        ),
      ],
      [
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id1, u128::MAX)],
        ),
        (
          OutPoint {
            txid: txid2,
            vout: 0,
          },
          vec![(id0, u128::MAX)],
        ),
      ],
    );
  }

  #[test]
  fn edicts_over_max_inputs_are_ignored() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX / 2,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX / 2),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX / 2,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX / 2)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: u128::MAX,
            output: 0,
          }],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX / 2,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, u128::MAX / 2)],
      )],
    );
  }

  #[test]
  fn edicts_may_transfer_runes_to_op_return_outputs() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 1,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          burned: u128::MAX,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [],
    );
  }

  #[test]
  fn outputs_with_no_runes_have_no_balance() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])],
    );
  }

  #[test]
  fn edicts_which_transfer_no_runes_to_output_create_no_balance_entry() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![
          Edict {
            id: RuneId::default(),
            amount: u128::MAX,
            output: 0,
          },
          Edict {
            id: RuneId::default(),
            amount: 0,
            output: 1,
          },
        ],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])],
    );
  }

  #[test]
  fn split_in_etching() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: 0,
          output: 5,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      4,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (OutPoint { txid, vout: 0 }, vec![(id, u128::MAX / 4 + 1)]),
        (OutPoint { txid, vout: 1 }, vec![(id, u128::MAX / 4 + 1)]),
        (OutPoint { txid, vout: 2 }, vec![(id, u128::MAX / 4 + 1)]),
        (OutPoint { txid, vout: 3 }, vec![(id, u128::MAX / 4)]),
      ],
    );
  }

  #[test]
  fn split_in_etching_with_preceding_edict() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![
          Edict {
            id: RuneId::default(),
            amount: 1000,
            output: 0,
          },
          Edict {
            id: RuneId::default(),
            amount: 0,
            output: 5,
          },
        ],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      4,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (
          OutPoint { txid, vout: 0 },
          vec![(id, 1000 + (u128::MAX - 1000) / 4 + 1)],
        ),
        (
          OutPoint { txid, vout: 1 },
          vec![(id, (u128::MAX - 1000) / 4 + 1)],
        ),
        (
          OutPoint { txid, vout: 2 },
          vec![(id, (u128::MAX - 1000) / 4 + 1)],
        ),
        (
          OutPoint { txid, vout: 3 },
          vec![(id, (u128::MAX - 1000) / 4)],
        ),
      ],
    );
  }

  #[test]
  fn split_in_etching_with_following_edict() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![
          Edict {
            id: RuneId::default(),
            amount: 0,
            output: 5,
          },
          Edict {
            id: RuneId::default(),
            amount: 1000,
            output: 0,
          },
        ],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      4,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (OutPoint { txid, vout: 0 }, vec![(id, u128::MAX / 4 + 1)]),
        (OutPoint { txid, vout: 1 }, vec![(id, u128::MAX / 4 + 1)]),
        (OutPoint { txid, vout: 2 }, vec![(id, u128::MAX / 4 + 1)]),
        (OutPoint { txid, vout: 3 }, vec![(id, u128::MAX / 4)]),
      ],
    );
  }

  #[test]
  fn split_with_amount_in_etching() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: 1000,
          output: 5,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(4000),
          ..default()
        }),
        ..default()
      },
      4,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: 4000,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (OutPoint { txid, vout: 0 }, vec![(id, 1000)]),
        (OutPoint { txid, vout: 1 }, vec![(id, 1000)]),
        (OutPoint { txid, vout: 2 }, vec![(id, 1000)]),
        (OutPoint { txid, vout: 3 }, vec![(id, 1000)]),
      ],
    );
  }

  #[test]
  fn split_in_etching_with_amount_with_preceding_edict() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![
          Edict {
            id: RuneId::default(),
            amount: u128::MAX - 3000,
            output: 0,
          },
          Edict {
            id: RuneId::default(),
            amount: 1000,
            output: 5,
          },
        ],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      4,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (OutPoint { txid, vout: 0 }, vec![(id, u128::MAX - 2000)]),
        (OutPoint { txid, vout: 1 }, vec![(id, 1000)]),
        (OutPoint { txid, vout: 2 }, vec![(id, 1000)]),
      ],
    );
  }

  #[test]
  fn split_in_etching_with_amount_with_following_edict() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![
          Edict {
            id: RuneId::default(),
            amount: 1000,
            output: 5,
          },
          Edict {
            id: RuneId::default(),
            amount: u128::MAX,
            output: 0,
          },
        ],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      4,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (
          OutPoint { txid, vout: 0 },
          vec![(id, u128::MAX - 4000 + 1000)],
        ),
        (OutPoint { txid, vout: 1 }, vec![(id, 1000)]),
        (OutPoint { txid, vout: 2 }, vec![(id, 1000)]),
        (OutPoint { txid, vout: 3 }, vec![(id, 1000)]),
      ],
    );
  }

  #[test]
  fn split() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      outputs: 2,
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: 0,
            output: 3,
          }],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id, u128::MAX / 2 + 1)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 1,
          },
          vec![(id, u128::MAX / 2)],
        ),
      ],
    );
  }

  #[test]
  fn split_with_preceding_edict() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      outputs: 2,
      op_return: Some(
        Runestone {
          edicts: vec![
            Edict {
              id,
              amount: 1000,
              output: 0,
            },
            Edict {
              id,
              amount: 0,
              output: 3,
            },
          ],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id, 1000 + (u128::MAX - 1000) / 2 + 1)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 1,
          },
          vec![(id, (u128::MAX - 1000) / 2)],
        ),
      ],
    );
  }

  #[test]
  fn split_with_following_edict() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      outputs: 2,
      op_return: Some(
        Runestone {
          edicts: vec![
            Edict {
              id,
              amount: 0,
              output: 3,
            },
            Edict {
              id,
              amount: 1000,
              output: 1,
            },
          ],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id, u128::MAX / 2 + 1)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 1,
          },
          vec![(id, u128::MAX / 2)],
        ),
      ],
    );
  }

  #[test]
  fn split_with_amount() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      outputs: 2,
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: 1000,
            output: 3,
          }],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id, u128::MAX - 1000)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 1,
          },
          vec![(id, 1000)],
        ),
      ],
    );
  }

  #[test]
  fn split_with_amount_with_preceding_edict() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      outputs: 4,
      op_return: Some(
        Runestone {
          edicts: vec![
            Edict {
              id,
              amount: u128::MAX - 2000,
              output: 0,
            },
            Edict {
              id,
              amount: 1000,
              output: 5,
            },
          ],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id, u128::MAX - 2000 + 1000)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 1,
          },
          vec![(id, 1000)],
        ),
      ],
    );
  }

  #[test]
  fn split_with_amount_with_following_edict() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      outputs: 4,
      op_return: Some(
        Runestone {
          edicts: vec![
            Edict {
              id,
              amount: 1000,
              output: 5,
            },
            Edict {
              id,
              amount: u128::MAX,
              output: 0,
            },
          ],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id, u128::MAX - 4000 + 1000)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 1,
          },
          vec![(id, 1000)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 2,
          },
          vec![(id, 1000)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 3,
          },
          vec![(id, 1000)],
        ),
      ],
    );
  }

  #[test]
  fn etching_may_specify_symbol() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          symbol: Some('$'),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          symbol: Some('$'),
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])],
    );
  }

  #[test]
  fn allocate_all_remaining_runes_in_etching() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: 0,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, u128::MAX)])],
    );
  }

  #[test]
  fn allocate_all_remaining_runes_in_inputs() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: u128::MAX,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      outputs: 2,
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: 0,
            output: 1,
          }],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 1,
        },
        vec![(id, u128::MAX)],
      )],
    );
  }

  #[test]
  fn rune_can_be_minted_without_edict() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          mints: 0,
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          ..default()
        },
      )],
      [],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          mints: 1,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: 0,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );
  }

  #[test]
  fn rune_cannot_be_minted_less_than_limit_amount() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          mints: 0,
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          ..default()
        },
      )],
      [],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      outputs: 2,
      op_return: Some(
        Runestone {
          mint: Some(id),
          edicts: vec![Edict {
            id,
            amount: 111,
            output: 0,
          }],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          mints: 1,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: 0,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );
  }

  #[test]
  fn etching_with_amount_can_be_minted() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            cap: Some(100),
            amount: Some(1000),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          premine: 0,
          mints: 0,
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          ..default()
        },
      )],
      [],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(3, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: 1000,
            output: 0,
          }],
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          mints: 1,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: 0,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );

    // claim the rune
    let txid2 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(4, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: 1000,
            output: 0,
          }],
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          mints: 2,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: 0,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (
          OutPoint {
            txid: txid2,
            vout: 0,
          },
          vec![(id, 1000)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id, 1000)],
        ),
      ],
    );

    // claim the rune in a burn runestone
    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(5, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          pointer: Some(10),
          mint: Some(id),
          edicts: vec![Edict {
            id,
            amount: 1000,
            output: 0,
          }],
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          burned: 1000,
          etching: txid0,
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          mints: 3,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: 0,
          timestamp: id.block,
          ..default()
        },
      )],
      [
        (
          OutPoint {
            txid: txid2,
            vout: 0,
          },
          vec![(id, 1000)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id, 1000)],
        ),
      ],
    );
  }

  #[test]
  fn open_mints_can_be_limited_with_offset_end() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            offset: (None, Some(2)),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    let mut entry = RuneEntry {
      block: id.block,
      etching: txid0,
      spaced_rune: SpacedRune {
        rune: Rune(RUNE),
        spacers: 0,
      },
      terms: Some(Terms {
        amount: Some(1000),
        offset: (None, Some(2)),
        cap: Some(100),
        ..default()
      }),
      timestamp: id.block,
      ..default()
    };

    context.assert_runes([(id, entry)], []);

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    entry.mints += 1;

    context.assert_runes(
      [(id, entry)],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(3, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(id, entry)],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );
  }

  #[test]
  fn open_mints_can_be_limited_with_offset_start() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            offset: (Some(2), None),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    let mut entry = RuneEntry {
      block: id.block,
      etching: txid0,
      spaced_rune: SpacedRune {
        rune: Rune(RUNE),
        spacers: 0,
      },
      terms: Some(Terms {
        amount: Some(1000),
        offset: (Some(2), None),
        cap: Some(100),
        ..default()
      }),
      timestamp: id.block,
      ..default()
    };

    context.assert_runes([(id, entry)], []);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes([(id, entry)], []);

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(3, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    entry.mints += 1;

    context.assert_runes(
      [(id, entry)],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );
  }

  #[test]
  fn open_mints_can_be_limited_with_height_start() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            height: (Some(10), None),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    let mut entry = RuneEntry {
      block: id.block,
      etching: txid0,
      spaced_rune: SpacedRune {
        rune: Rune(RUNE),
        spacers: 0,
      },
      terms: Some(Terms {
        amount: Some(1000),
        height: (Some(10), None),
        cap: Some(100),
        ..default()
      }),
      timestamp: id.block,
      ..default()
    };

    context.assert_runes([(id, entry)], []);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes([(id, entry)], []);

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(3, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    entry.mints += 1;

    context.assert_runes(
      [(id, entry)],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );
  }

  #[test]
  fn open_mints_can_be_limited_with_height_end() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            height: (None, Some(10)),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    let mut entry = RuneEntry {
      block: id.block,
      etching: txid0,
      spaced_rune: SpacedRune {
        rune: Rune(RUNE),
        spacers: 0,
      },
      terms: Some(Terms {
        amount: Some(1000),
        height: (None, Some(10)),
        cap: Some(100),
        ..default()
      }),
      timestamp: id.block,
      ..default()
    };

    context.assert_runes([(id, entry)], []);

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    entry.mints += 1;

    context.assert_runes(
      [(id, entry)],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(3, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(id, entry)],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );
  }

  #[test]
  fn open_mints_must_be_ended_with_etched_height_plus_offset_end() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            height: (None, Some(100)),
            offset: (None, Some(2)),
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    let mut entry = RuneEntry {
      block: id.block,
      etching: txid0,
      spaced_rune: SpacedRune {
        rune: Rune(RUNE),
        spacers: 0,
      },
      terms: Some(Terms {
        amount: Some(1000),
        height: (None, Some(100)),
        offset: (None, Some(2)),
        cap: Some(100),
      }),
      timestamp: id.block,
      ..default()
    };

    context.assert_runes([(id, entry)], []);

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);
    entry.mints += 1;

    context.assert_runes(
      [(id, entry)],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(3, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(id, entry)],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );
  }

  #[test]
  fn open_mints_must_be_ended_with_height_end() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            height: (None, Some(10)),
            offset: (None, Some(100)),
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    let mut entry = RuneEntry {
      block: id.block,
      etching: txid0,
      spaced_rune: SpacedRune {
        rune: Rune(RUNE),
        spacers: 0,
      },
      terms: Some(Terms {
        amount: Some(1000),
        height: (None, Some(10)),
        offset: (None, Some(100)),
        cap: Some(100),
      }),
      timestamp: id.block,
      ..default()
    };

    context.assert_runes([(id, entry)], []);

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);
    entry.mints += 1;

    context.assert_runes(
      [(id, entry)],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(3, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(id, entry)],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );
  }

  #[test]
  fn open_mints_must_be_started_with_height_start() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            height: (Some(11), None),
            offset: (Some(1), None),
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    let mut entry0 = RuneEntry {
      block: id.block,
      etching: txid0,
      spaced_rune: SpacedRune {
        rune: Rune(RUNE),
        spacers: 0,
      },
      terms: Some(Terms {
        amount: Some(1000),
        height: (Some(11), None),
        offset: (Some(1), None),
        cap: Some(100),
      }),
      timestamp: id.block,
      ..default()
    };

    context.mine_blocks(1);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes([(id, entry0)], []);

    context.mine_blocks(1);

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(3, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    entry0.mints += 1;

    context.assert_runes(
      [(id, entry0)],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );
  }

  #[test]
  fn open_mints_must_be_started_with_etched_height_plus_offset_start() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            height: (Some(9), None),
            offset: (Some(3), None),
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    let mut entry = RuneEntry {
      block: id.block,
      etching: txid0,
      spaced_rune: SpacedRune {
        rune: Rune(RUNE),
        spacers: 0,
      },
      terms: Some(Terms {
        amount: Some(1000),
        height: (Some(9), None),
        offset: (Some(3), None),
        cap: Some(100),
      }),
      timestamp: id.block,
      ..default()
    };

    context.mine_blocks(1);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes([(id, entry)], []);

    context.mine_blocks(1);

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(3, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    entry.mints += 1;

    context.assert_runes(
      [(id, entry)],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );
  }

  #[test]
  fn open_mints_with_offset_end_zero_can_be_premined() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: 1111,
          output: 0,
        }],
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(1111),
          terms: Some(Terms {
            amount: Some(1000),
            offset: (None, Some(0)),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          terms: Some(Terms {
            amount: Some(1000),
            offset: (None, Some(0)),
            ..default()
          }),
          timestamp: id.block,
          premine: 1111,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, 1111)])],
    );

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      outputs: 2,
      op_return: Some(
        Runestone {
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          terms: Some(Terms {
            amount: Some(1000),
            offset: (None, Some(0)),
            ..default()
          }),
          premine: 1111,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, 1111)])],
    );
  }

  #[test]
  fn open_mints_can_be_limited_to_cap() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(2),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(2),
            ..default()
          }),
          ..default()
        },
      )],
      [],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: 1000,
            output: 0,
          }],
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          mints: 1,
          etching: txid0,
          terms: Some(Terms {
            cap: Some(2),
            amount: Some(1000),
            ..default()
          }),
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );

    let txid2 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(3, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: 1000,
            output: 0,
          }],
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(2),
            ..default()
          }),
          mints: 2,
          ..default()
        },
      )],
      [
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id, 1000)],
        ),
        (
          OutPoint {
            txid: txid2,
            vout: 0,
          },
          vec![(id, 1000)],
        ),
      ],
    );

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(4, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: 1000,
            output: 0,
          }],
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(2),
            ..default()
          }),
          mints: 2,
          ..default()
        },
      )],
      [
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id, 1000)],
        ),
        (
          OutPoint {
            txid: txid2,
            vout: 0,
          },
          vec![(id, 1000)],
        ),
      ],
    );
  }

  #[test]
  fn open_mints_without_a_cap_are_unmintable() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            offset: (None, Some(2)),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          terms: Some(Terms {
            amount: Some(1000),
            offset: (None, Some(2)),
            ..default()
          }),
          ..default()
        },
      )],
      [],
    );

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: 1000,
            output: 0,
          }],
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          mints: 0,
          etching: txid0,
          terms: Some(Terms {
            amount: Some(1000),
            offset: (None, Some(2)),
            ..default()
          }),
          ..default()
        },
      )],
      [],
    );
  }

  #[test]
  fn open_mint_claims_can_use_split() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          timestamp: id.block,
          ..default()
        },
      )],
      [],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(3, 0, 0, Witness::new())],
      outputs: 2,
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: 0,
            output: 3,
          }],
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          timestamp: id.block,
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          mints: 1,
          ..default()
        },
      )],
      [
        (
          OutPoint {
            txid: txid1,
            vout: 0,
          },
          vec![(id, 500)],
        ),
        (
          OutPoint {
            txid: txid1,
            vout: 1,
          },
          vec![(id, 500)],
        ),
      ],
    );
  }

  #[test]
  fn runes_can_be_etched_and_premined_in_the_same_transaction() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(2000),
          terms: Some(Terms {
            amount: Some(1000),
            ..default()
          }),
          ..default()
        }),
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: 2000,
          output: 0,
        }],
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          terms: Some(Terms {
            amount: Some(1000),
            ..default()
          }),
          timestamp: id.block,
          premine: 2000,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, 2000)])],
    );
  }

  #[test]
  fn omitted_edicts_defaults_to_mint_amount() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            offset: (None, Some(1)),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          terms: Some(Terms {
            amount: None,
            offset: (None, Some(1)),
            ..default()
          }),
          timestamp: id.block,
          ..default()
        },
      )],
      [],
    );
  }

  #[test]
  fn premines_can_claim_over_mint_amount() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(2000),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(1),
            ..default()
          }),
          ..default()
        }),
        edicts: vec![Edict {
          id: RuneId::default(),
          amount: 2000,
          output: 0,
        }],
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(1),
            ..default()
          }),
          timestamp: id.block,
          premine: 2000,
          mints: 0,
          ..default()
        },
      )],
      [(OutPoint { txid, vout: 0 }, vec![(id, 2000)])],
    );
  }

  #[test]
  fn transactions_cannot_claim_more_than_mint_amount() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: 2000,
            output: 0,
          }],
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          timestamp: id.block,
          mints: 1,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );
  }

  #[test]
  fn multiple_edicts_in_one_transaction_may_claim_open_mint() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          timestamp: id.block,
          ..default()
        },
      )],
      [],
    );

    let txid1 = context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(2, 0, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![
            Edict {
              id,
              amount: 500,
              output: 0,
            },
            Edict {
              id,
              amount: 500,
              output: 0,
            },
            Edict {
              id,
              amount: 500,
              output: 0,
            },
          ],
          mint: Some(id),
          ..default()
        }
        .encipher(),
      ),
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          terms: Some(Terms {
            amount: Some(1000),
            cap: Some(100),
            ..default()
          }),
          timestamp: id.block,
          mints: 1,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid1,
          vout: 0,
        },
        vec![(id, 1000)],
      )],
    );
  }

  #[test]
  fn commits_are_not_valid_in_non_taproot_witnesses() {
    let context = Context::builder().arg("--index-runes").build();

    let block_count = context.index.block_count().unwrap().into_usize();

    context.mine_blocks(1);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(block_count, 0, 0, Witness::new())],
      p2tr: false,
      ..default()
    });

    context.mine_blocks(Runestone::COMMIT_CONFIRMATIONS.into());

    let mut witness = Witness::new();

    let runestone = Runestone {
      etching: Some(Etching {
        rune: Some(Rune(RUNE)),
        terms: Some(Terms {
          amount: Some(1000),
          ..default()
        }),
        ..default()
      }),
      ..default()
    };

    let tapscript = script::Builder::new()
      .push_slice::<&PushBytes>(
        runestone
          .etching
          .unwrap()
          .rune
          .unwrap()
          .commitment()
          .as_slice()
          .try_into()
          .unwrap(),
      )
      .into_script();

    witness.push(tapscript);

    witness.push([]);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(block_count + 1, 1, 0, witness)],
      op_return: Some(runestone.encipher()),
      outputs: 1,
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes([], []);
  }

  #[test]
  fn immature_commits_are_not_valid() {
    let context = Context::builder().arg("--index-runes").build();

    let block_count = context.index.block_count().unwrap().into_usize();

    context.mine_blocks(1);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(block_count, 0, 0, Witness::new())],
      p2tr: true,
      ..default()
    });

    context.mine_blocks((Runestone::COMMIT_CONFIRMATIONS - 2).into());

    let mut witness = Witness::new();

    let runestone = Runestone {
      etching: Some(Etching {
        rune: Some(Rune(RUNE)),
        terms: Some(Terms {
          amount: Some(1000),
          ..default()
        }),
        ..default()
      }),
      ..default()
    };

    let tapscript = script::Builder::new()
      .push_slice::<&PushBytes>(
        runestone
          .etching
          .unwrap()
          .rune
          .unwrap()
          .commitment()
          .as_slice()
          .try_into()
          .unwrap(),
      )
      .into_script();

    witness.push(tapscript);

    witness.push([]);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(block_count + 1, 1, 0, witness)],
      op_return: Some(runestone.encipher()),
      outputs: 1,
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes([], []);
  }

  #[test]
  fn immature_commits_are_not_valid_even_when_bitcoind_is_ahead() {
    let context = Context::builder().arg("--index-runes").build();

    let block_count = context.index.block_count().unwrap().into_usize();

    context.mine_blocks_with_update(1, false);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(block_count, 0, 0, Witness::new())],
      p2tr: true,
      ..default()
    });

    context.mine_blocks_with_update((Runestone::COMMIT_CONFIRMATIONS - 2).into(), false);

    let mut witness = Witness::new();

    let runestone = Runestone {
      etching: Some(Etching {
        rune: Some(Rune(RUNE)),
        terms: Some(Terms {
          amount: Some(1000),
          ..default()
        }),
        ..default()
      }),
      ..default()
    };

    let tapscript = script::Builder::new()
      .push_slice::<&PushBytes>(
        runestone
          .etching
          .unwrap()
          .rune
          .unwrap()
          .commitment()
          .as_slice()
          .try_into()
          .unwrap(),
      )
      .into_script();

    witness.push(tapscript);

    witness.push([]);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(block_count + 1, 1, 0, witness)],
      op_return: Some(runestone.encipher()),
      outputs: 1,
      ..default()
    });

    context.mine_blocks_with_update(2, false);

    context.mine_blocks_with_update(1, true);

    context.assert_runes([], []);
  }

  #[test]
  fn etchings_are_not_valid_without_commitment() {
    let context = Context::builder().arg("--index-runes").build();

    let block_count = context.index.block_count().unwrap().into_usize();

    context.mine_blocks(1);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(block_count, 0, 0, Witness::new())],
      p2tr: true,
      ..default()
    });

    context.mine_blocks(Runestone::COMMIT_CONFIRMATIONS.into());

    let mut witness = Witness::new();

    let runestone = Runestone {
      etching: Some(Etching {
        rune: Some(Rune(RUNE)),
        terms: Some(Terms {
          amount: Some(1000),
          ..default()
        }),
        ..default()
      }),
      ..default()
    };

    let tapscript = script::Builder::new()
      .push_slice::<&PushBytes>([].as_slice().try_into().unwrap())
      .into_script();

    witness.push(tapscript);

    witness.push([]);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(block_count + 1, 1, 0, witness)],
      op_return: Some(runestone.encipher()),
      outputs: 1,
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes([], []);
  }

  #[test]
  fn tx_commits_to_rune_ignores_invalid_script() {
    let context = Context::builder().arg("--index-runes").build();

    context.mine_blocks(1);

    let runestone = Runestone {
      etching: Some(Etching {
        rune: Some(Rune(RUNE)),
        terms: Some(Terms {
          amount: Some(1000),
          ..default()
        }),
        ..default()
      }),
      ..default()
    };

    let mut witness = Witness::new();

    witness.push([opcodes::all::OP_PUSHDATA4.to_u8()]);
    witness.push([]);

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(1, 0, 0, witness)],
      op_return: Some(runestone.encipher()),
      outputs: 1,
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes([], []);
  }

  #[test]
  fn edict_with_amount_zero_and_no_destinations_is_ignored() {
    let context = Context::builder().arg("--index-runes").build();

    let (txid0, id) = context.etch(
      Runestone {
        etching: Some(Etching {
          rune: Some(Rune(RUNE)),
          premine: Some(u128::MAX),
          ..default()
        }),
        ..default()
      },
      1,
    );

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [(
        OutPoint {
          txid: txid0,
          vout: 0,
        },
        vec![(id, u128::MAX)],
      )],
    );

    context.core.broadcast_tx(TransactionTemplate {
      inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())],
      op_return: Some(
        Runestone {
          edicts: vec![Edict {
            id,
            amount: 0,
            output: 1,
          }],
          ..default()
        }
        .encipher(),
      ),
      outputs: 0,
      ..default()
    });

    context.mine_blocks(1);

    context.assert_runes(
      [(
        id,
        RuneEntry {
          block: id.block,
          etching: txid0,
          spaced_rune: SpacedRune {
            rune: Rune(RUNE),
            spacers: 0,
          },
          premine: u128::MAX,
          burned: u128::MAX,
          timestamp: id.block,
          ..default()
        },
      )],
      [],
    );
  }

  #[test]
  fn genesis_rune() {
    assert_eq!(
      Chain::Mainnet.first_rune_height(),
      SUBSIDY_HALVING_INTERVAL * 4,
    );

    Context::builder()
      .chain(Chain::Mainnet)
      .arg("--index-runes")
      .build()
      .assert_runes(
        [(
          RuneId { block: 1, tx: 0 },
          RuneEntry {
            block: 1,
            burned: 0,
            divisibility: 0,
            etching: Txid::all_zeros(),
            mints: 0,
            number: 0,
            premine: 0,
            spaced_rune: SpacedRune {
              rune: Rune(2055900680524219742),
              spacers: 128,
            },
            symbol: Some('\u{29C9}'),
            terms: Some(Terms {
              amount: Some(1),
              cap: Some(u128::MAX),
              height: (
                Some((SUBSIDY_HALVING_INTERVAL * 4).into()),
                Some((SUBSIDY_HALVING_INTERVAL * 5).into()),
              ),
              offset: (None, None),
            }),
            timestamp: 0,
            turbo: true,
          },
        )],
        [],
      );
  }
}
