miden-objects 0.17.0-rc.6

Canonical Protobuf representations for Miden protocol objects
Documentation
use alloc::string::{String, ToString};
use alloc::vec;

use miden_protocol::Word;

use crate::decoded::account::test_utils::account_patch;
use crate::test_utils::error_source;
use crate::{ConversionError, DecodeMessage, Verify, proto};

#[test]
fn vault_patch_entry_defers_asset_id_validation() {
    let decoded = proto::account::AccountVaultPatchEntry {
        asset_id: Some(Word::empty().into()),
        value: Some(Word::empty().into()),
    }
    .decode_fields()
    .unwrap();
    assert_eq!(decoded.asset_id, Word::empty());
    assert!(decoded.verify().is_err());
}

#[test]
fn vault_patch_verifies_duplicate_ids_and_asset_values() {
    use miden_protocol::account::{AccountId, AccountIdVersion, AccountType, AssetCallbackFlag};
    let id = miden_protocol::asset::AssetId::new_fungible(AccountId::dummy(
        [7; 15],
        AccountIdVersion::Version1,
        AccountType::Private,
        AssetCallbackFlag::Disabled,
    ));
    let entry = proto::account::AccountVaultPatchEntry {
        asset_id: Some(id.to_word().into()),
        value: Some(Word::from([2_u32, 0, 0, 0]).into()),
    };
    assert!(
        proto::account::AccountVaultPatch { entries: vec![entry.clone()] }
            .decode_fields()
            .unwrap()
            .verify()
            .is_ok()
    );
    let decoded = proto::account::AccountVaultPatch {
        entries: vec![entry.clone(), entry.clone()],
    }
    .decode_fields()
    .unwrap();
    assert!(matches!(
        error_source::<crate::decoded::account::VaultPatchError>(&decoded.verify().unwrap_err()),
        Some(crate::decoded::account::VaultPatchError::DuplicateAssetId(actual)) if *actual == id
    ));
    let invalid = proto::account::AccountVaultPatchEntry {
        value: Some(Word::from([1_u32, 2, 0, 0]).into()),
        ..entry
    };
    assert!(
        proto::account::AccountVaultPatch { entries: vec![invalid] }
            .decode_fields()
            .unwrap()
            .verify()
            .is_err()
    );
}

#[test]
fn private_account_update_verifies_empty_payload() {
    let decoded = proto::account::PrivateAccountUpdate {}.decode_fields().unwrap();
    assert_eq!(
        decoded.verify().unwrap(),
        miden_protocol::account::AccountUpdateDetails::Private
    );
}

#[test]
fn storage_map_patch_rejects_empty_updates_after_decoding() {
    let wire = proto::account::StorageMapPatch {
        operation: Some(proto::account::storage_map_patch::Operation::Update(
            proto::account::StorageMapPatchEntries { entries: vec![] },
        )),
    };
    assert!(matches!(
        wire.decode_fields().unwrap().verify(),
        Err(crate::decoded::account::StorageMapPatchError::EmptyUpdate)
    ));
}

#[test]
fn storage_map_patch_rejects_duplicate_keys_after_decoding() {
    use proto::account::storage_map_patch::Operation;

    let entry = proto::account::StorageMapEntry {
        key: Some(Word::empty().into()),
        value: Some(Word::empty().into()),
    };
    let entries = proto::account::StorageMapPatchEntries { entries: vec![entry.clone(), entry] };
    for operation in [Operation::Create(entries.clone()), Operation::Update(entries)] {
        let decoded = proto::account::StorageMapPatch { operation: Some(operation) }
            .decode_fields()
            .unwrap();
        assert!(matches!(
            decoded.verify(),
            Err(crate::decoded::account::StorageMapPatchError::DuplicateKey(_))
        ));
    }
}

#[test]
fn decoded_storage_value_patch_is_a_typed_oneof() {
    use crate::proto::account::StorageValuePatch;
    use crate::proto::account::storage_value_patch::Operation;
    let decoded = StorageValuePatch { operation: Some(Operation::Remove(())) }
        .decode_fields()
        .unwrap();
    assert_eq!(decoded.verify().unwrap(), miden_protocol::account::StorageValuePatch::Remove);
}

#[test]
fn storage_slot_patch_defers_slot_name_validation() {
    let wire = proto::account::StorageSlotPatch {
        slot_name: String::new(),
        patch: Some(proto::account::storage_slot_patch::Patch::Value(
            proto::account::StorageValuePatch {
                operation: Some(proto::account::storage_value_patch::Operation::Remove(())),
            },
        )),
    };
    assert!(wire.decode_fields().unwrap().verify().is_err());
}

#[test]
fn account_patch_protobuf_rejects_unspecified_version_after_decoding() {
    let error = proto::account::AccountPatch {
        version: proto::account::AccountPatchVersion::Unspecified as i32,
        ..proto::account::AccountPatch::from(account_patch())
    }
    .decode_fields()
    .unwrap()
    .verify()
    .map_err(ConversionError::new)
    .unwrap_err();

    assert_eq!(error.to_string(), "account patch version is unspecified");
}