bee-block 1.0.1

Implementation of the IOTA protocol block layouts
Documentation
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use bee_block::{
    address::{Address, Ed25519Address},
    input::{Input, TreasuryInput, UtxoInput},
    output::{unlock_condition::AddressUnlockCondition, BasicOutput, Output, TreasuryOutput},
    payload::{
        milestone::MilestoneId,
        transaction::{RegularTransactionEssence, TransactionId},
        Payload,
    },
    protocol::protocol_parameters,
    rand::{
        bytes::rand_bytes_array,
        output::rand_inputs_commitment,
        payload::{rand_tagged_data_payload, rand_treasury_transaction_payload},
    },
    Error,
};
use packable::bounded::TryIntoBoundedU16Error;

const TRANSACTION_ID: &str = "0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649";
const ED25519_ADDRESS_1: &str = "0xd56da1eb7726ed482dfe9d457cf548c2ae2a6ce3e053dbf82f11223be476adb9";
const ED25519_ADDRESS_2: &str = "0xefda4275375ac3675abff85235fd25a1522a2044cc6027a31b310857246f18c0";

#[test]
fn kind() {
    assert_eq!(RegularTransactionEssence::KIND, 1);
}

#[test]
fn build_valid() {
    let protocol_parameters = protocol_parameters();
    let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap());
    let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap());
    let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap());
    let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap();
    let address = Address::from(Ed25519Address::new(bytes));
    let amount = 1_000_000;
    let output = Output::Basic(
        BasicOutput::build_with_amount(amount)
            .unwrap()
            .add_unlock_condition(AddressUnlockCondition::new(address).into())
            .finish(protocol_parameters.token_supply())
            .unwrap(),
    );

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .with_inputs(vec![input1, input2])
        .add_output(output)
        .finish(&protocol_parameters);

    assert!(essence.is_ok());
}

#[test]
fn build_valid_with_payload() {
    let protocol_parameters = protocol_parameters();
    let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap());
    let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap());
    let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap());
    let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap();
    let address = Address::from(Ed25519Address::new(bytes));
    let amount = 1_000_000;
    let output = Output::Basic(
        BasicOutput::build_with_amount(amount)
            .unwrap()
            .add_unlock_condition(AddressUnlockCondition::new(address).into())
            .finish(protocol_parameters.token_supply())
            .unwrap(),
    );
    let payload = Payload::from(rand_tagged_data_payload());

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .with_inputs(vec![input1, input2])
        .add_output(output)
        .with_payload(payload)
        .finish(&protocol_parameters);

    assert!(essence.is_ok());
}

#[test]
fn build_valid_add_inputs_outputs() {
    let protocol_parameters = protocol_parameters();
    let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap());
    let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap());
    let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap());
    let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap();
    let address = Address::from(Ed25519Address::new(bytes));
    let amount = 1_000_000;
    let output = Output::Basic(
        BasicOutput::build_with_amount(amount)
            .unwrap()
            .add_unlock_condition(AddressUnlockCondition::new(address).into())
            .finish(protocol_parameters.token_supply())
            .unwrap(),
    );

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .with_inputs(vec![input1, input2])
        .add_output(output)
        .finish(&protocol_parameters);

    assert!(essence.is_ok());
}

#[test]
fn build_invalid_payload_kind() {
    let protocol_parameters = protocol_parameters();
    let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap());
    let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap());
    let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap());
    let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap();
    let address = Address::from(Ed25519Address::new(bytes));
    let amount = 1_000_000;
    let output = Output::Basic(
        BasicOutput::build_with_amount(amount)
            .unwrap()
            .add_unlock_condition(AddressUnlockCondition::new(address).into())
            .finish(protocol_parameters.token_supply())
            .unwrap(),
    );
    let payload = rand_treasury_transaction_payload(protocol_parameters.token_supply());

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .with_inputs(vec![input1, input2])
        .add_output(output)
        .with_payload(payload.into())
        .finish(&protocol_parameters);

    assert!(matches!(essence, Err(Error::InvalidPayloadKind(4))));
}

#[test]
fn build_invalid_input_count_low() {
    let protocol_parameters = protocol_parameters();
    let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap();
    let address = Address::from(Ed25519Address::new(bytes));
    let amount = 1_000_000;
    let output = Output::Basic(
        BasicOutput::build_with_amount(amount)
            .unwrap()
            .add_unlock_condition(AddressUnlockCondition::new(address).into())
            .finish(protocol_parameters.token_supply())
            .unwrap(),
    );

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .add_output(output)
        .finish(&protocol_parameters);

    assert!(matches!(
        essence,
        Err(Error::InvalidInputCount(TryIntoBoundedU16Error::Invalid(0)))
    ));
}

#[test]
fn build_invalid_input_count_high() {
    let protocol_parameters = protocol_parameters();
    let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap());
    let input = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap());
    let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap();
    let address = Address::from(Ed25519Address::new(bytes));
    let amount = 1_000_000;
    let output = Output::Basic(
        BasicOutput::build_with_amount(amount)
            .unwrap()
            .add_unlock_condition(AddressUnlockCondition::new(address).into())
            .finish(protocol_parameters.token_supply())
            .unwrap(),
    );

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .with_inputs(vec![input; 129])
        .add_output(output)
        .finish(&protocol_parameters);

    assert!(matches!(
        essence,
        Err(Error::InvalidInputCount(TryIntoBoundedU16Error::Invalid(129)))
    ));
}

#[test]
fn build_invalid_output_count_low() {
    let protocol_parameters = protocol_parameters();
    let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap());
    let input = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap());

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .add_input(input)
        .finish(&protocol_parameters);

    assert!(matches!(
        essence,
        Err(Error::InvalidOutputCount(TryIntoBoundedU16Error::Invalid(0)))
    ));
}

#[test]
fn build_invalid_output_count_high() {
    let protocol_parameters = protocol_parameters();
    let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap());
    let input = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap());
    let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap();
    let address = Address::from(Ed25519Address::new(bytes));
    let amount = 1_000_000;
    let output = Output::Basic(
        BasicOutput::build_with_amount(amount)
            .unwrap()
            .add_unlock_condition(AddressUnlockCondition::new(address).into())
            .finish(protocol_parameters.token_supply())
            .unwrap(),
    );

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .add_input(input)
        .with_outputs(vec![output; 129])
        .finish(&protocol_parameters);

    assert!(matches!(
        essence,
        Err(Error::InvalidOutputCount(TryIntoBoundedU16Error::Invalid(129)))
    ));
}

#[test]
fn build_invalid_duplicate_utxo() {
    let protocol_parameters = protocol_parameters();
    let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap());
    let input = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap());
    let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap();
    let address = Address::from(Ed25519Address::new(bytes));
    let amount = 1_000_000;
    let output = Output::Basic(
        BasicOutput::build_with_amount(amount)
            .unwrap()
            .add_unlock_condition(AddressUnlockCondition::new(address).into())
            .finish(protocol_parameters.token_supply())
            .unwrap(),
    );

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .with_inputs(vec![input; 2])
        .add_output(output)
        .finish(&protocol_parameters);

    assert!(matches!(essence, Err(Error::DuplicateUtxo(_))));
}

#[test]
fn build_invalid_input_kind() {
    let protocol_parameters = protocol_parameters();
    let input = Input::Treasury(TreasuryInput::new(MilestoneId::new(rand_bytes_array())));
    let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap();
    let address = Address::from(Ed25519Address::new(bytes));
    let amount = 1_000_000;
    let output = Output::Basic(
        BasicOutput::build_with_amount(amount)
            .unwrap()
            .add_unlock_condition(AddressUnlockCondition::new(address).into())
            .finish(protocol_parameters.token_supply())
            .unwrap(),
    );

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .add_input(input)
        .add_output(output)
        .finish(&protocol_parameters);

    assert!(matches!(essence, Err(Error::InvalidInputKind(1))));
}

#[test]
fn build_invalid_output_kind() {
    let protocol_parameters = protocol_parameters();
    let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap());
    let input = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap());
    let amount = 1_000_000;
    let output = Output::Treasury(TreasuryOutput::new(amount, protocol_parameters.token_supply()).unwrap());

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .add_input(input)
        .add_output(output)
        .finish(&protocol_parameters);

    assert!(matches!(essence, Err(Error::InvalidOutputKind(2))));
}

#[test]
fn build_invalid_accumulated_output() {
    let protocol_parameters = protocol_parameters();
    let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap());
    let input = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap());

    let bytes1: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap();
    let address1 = Address::from(Ed25519Address::new(bytes1));
    let amount1 = protocol_parameters.token_supply() - 1_000_000;
    let output1 = Output::Basic(
        BasicOutput::build_with_amount(amount1)
            .unwrap()
            .add_unlock_condition(AddressUnlockCondition::new(address1).into())
            .finish(protocol_parameters.token_supply())
            .unwrap(),
    );

    let bytes2: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_2).unwrap();
    let address2 = Address::from(Ed25519Address::new(bytes2));
    let amount2 = 2_000_000;
    let output2 = Output::Basic(
        BasicOutput::build_with_amount(amount2)
            .unwrap()
            .add_unlock_condition(AddressUnlockCondition::new(address2).into())
            .finish(protocol_parameters.token_supply())
            .unwrap(),
    );

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .add_input(input)
        .with_outputs(vec![output1, output2])
        .finish(&protocol_parameters);

    assert!(matches!(essence, Err(Error::InvalidTransactionAmountSum(_))));
}

#[test]
fn getters() {
    let protocol_parameters = protocol_parameters();
    let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap());
    let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap());
    let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap());
    let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS_1).unwrap();
    let address = Address::from(Ed25519Address::new(bytes));
    let amount = 1_000_000;
    let outputs = vec![Output::Basic(
        BasicOutput::build_with_amount(amount)
            .unwrap()
            .add_unlock_condition(AddressUnlockCondition::new(address).into())
            .finish(protocol_parameters.token_supply())
            .unwrap(),
    )];
    let payload = Payload::from(rand_tagged_data_payload());

    let essence = RegularTransactionEssence::builder(rand_inputs_commitment())
        .with_inputs(vec![input1, input2])
        .with_outputs(outputs.clone())
        .with_payload(payload.clone())
        .finish(&protocol_parameters)
        .unwrap();

    assert_eq!(essence.outputs(), outputs.as_slice());
    assert_eq!(essence.payload().unwrap(), &payload);
}