ic-host-tools 0.29.0

Host-side Candid interfaces, ICP responses and certificate decoding
Documentation
use super::*;

fn limits(bytes: usize) -> DecodeLimits {
    DecodeLimits {
        reply_bytes: bytes,
        decoding_quota: 4096,
        skipping_quota: 4096,
        type_entries: 64,
        header_bytes: 1024,
    }
}

#[test]
fn exact_budget_and_repeated_replies_are_charged() {
    let bytes = ic_platform::candid::contract::encode_one(42_u64).unwrap();
    let mut remaining = bytes.len() * 2;
    for _ in 0..2 {
        assert_eq!(
            decode_one::<u64>(&bytes, limits(bytes.len()), &mut remaining).unwrap(),
            42
        );
    }
    assert_eq!(remaining, 0);
    assert!(matches!(
        decode_one::<u64>(&bytes, limits(bytes.len()), &mut remaining),
        Err(DecodeError::AggregateBytes)
    ));
    assert_eq!(remaining, 0);
    remaining = bytes.len() - 1;
    assert!(matches!(
        decode_one::<u64>(&bytes, limits(bytes.len()), &mut remaining),
        Err(DecodeError::AggregateBytes)
    ));
    assert_eq!(remaining, bytes.len() - 1);
}

#[test]
fn byte_admission_precedes_decode_and_never_underflows() {
    let bytes = b"not candid";
    let mut remaining = bytes.len();
    assert!(matches!(
        decode_one::<u64>(bytes, limits(bytes.len() - 1), &mut remaining),
        Err(DecodeError::ReplyBytes)
    ));
    assert_eq!(remaining, bytes.len());
    assert!(matches!(
        decode_one::<u64>(bytes, limits(bytes.len()), &mut remaining),
        Err(DecodeError::Candid(_))
    ));
    assert_eq!(remaining, 0);
}

#[test]
fn caller_decoder_quotas_are_enforced() {
    let bytes = ic_platform::candid::contract::encode_one(vec![42_u64; 128]).unwrap();
    for constrained in [
        DecodeLimits {
            decoding_quota: 0,
            ..limits(bytes.len())
        },
        DecodeLimits {
            type_entries: 0,
            ..limits(bytes.len())
        },
        DecodeLimits {
            header_bytes: 0,
            ..limits(bytes.len())
        },
    ] {
        let mut remaining = bytes.len();
        assert!(matches!(
            decode_one::<Vec<u64>>(&bytes, constrained, &mut remaining),
            Err(DecodeError::Candid(_))
        ));
        assert_eq!(remaining, 0);
    }
}

#[test]
fn skipped_fields_consume_the_selected_work_quota() {
    let bytes = ic_platform::candid::contract::encode_one(vec![42_u64; 128]).unwrap();
    let mut remaining = bytes.len();
    let constrained = DecodeLimits {
        skipping_quota: 0,
        ..limits(bytes.len())
    };
    assert!(matches!(
        decode_one::<ic_platform::candid::contract::Reserved>(&bytes, constrained, &mut remaining),
        Err(DecodeError::Candid(_))
    ));
    assert_eq!(remaining, 0);
}

#[test]
fn extra_arguments_follow_candid_compatibility_and_skipping_limits() {
    let bytes = ic_platform::candid::contract::encode_args((42_u64, "future argument")).unwrap();
    let mut remaining = bytes.len();
    assert_eq!(
        decode_one::<u64>(&bytes, limits(bytes.len()), &mut remaining).unwrap(),
        42
    );
    assert_eq!(remaining, 0);
    remaining = bytes.len();
    let constrained = DecodeLimits {
        skipping_quota: 0,
        ..limits(bytes.len())
    };
    assert!(matches!(
        decode_one::<u64>(&bytes, constrained, &mut remaining),
        Err(DecodeError::Candid(_))
    ));
    assert_eq!(remaining, 0);
}

#[test]
fn incompatible_values_and_trailing_bytes_preserve_decoder_errors() {
    let incompatible = ic_platform::candid::contract::encode_one("wrong type").unwrap();
    let mut trailing = ic_platform::candid::contract::encode_one(42_u64).unwrap();
    trailing.push(0xff);
    for bytes in [incompatible, trailing] {
        let mut remaining = bytes.len();
        let error = decode_one::<u64>(&bytes, limits(bytes.len()), &mut remaining).unwrap_err();
        assert!(matches!(error, DecodeError::Candid(_)));
        assert!(
            std::error::Error::source(&error)
                .unwrap()
                .downcast_ref::<ic_platform::candid::contract::Error>()
                .is_some()
        );
        let selected = limits(bytes.len());
        let codec::DecodeError::Candid(expected) = codec::decode_one::<u64>(
            &bytes,
            codec::DecodeLimits {
                reply_bytes: selected.reply_bytes,
                decoding_quota: selected.decoding_quota,
                skipping_quota: selected.skipping_quota,
                type_entries: selected.type_entries,
                header_bytes: selected.header_bytes,
            },
        )
        .unwrap_err() else {
            panic!("expected the Platform decoder's typed failure");
        };
        let DecodeError::Candid(actual) = error else {
            panic!("expected the preserved Candid cause");
        };
        assert_eq!(actual.to_string(), expected.to_string());
        assert_eq!(remaining, 0);
    }
}