canic-host 0.110.38

Host-side App build and desired-state Fleet reconciliation library for Canic workspaces
Documentation
use super::*;

fn reply() -> BlocksReply {
    let operator = Principal::self_authenticating(b"operator");
    let destination = Principal::from_slice(&[1, 1]);
    let mut memo = Vec::new();
    ciborium::ser::into_writer(
        &ciborium::value::Value::Array(vec![ciborium::value::Value::Bytes(
            destination.as_slice().to_vec(),
        )]),
        &mut memo,
    )
    .unwrap();
    let tx = Value::Map(BTreeMap::from([
        ("op".into(), Value::Text("burn".into())),
        (
            "from".into(),
            Value::Array(vec![Value::Blob(operator.as_slice().to_vec().into())]),
        ),
        ("amt".into(), Value::Nat(1000_u64.into())),
        ("memo".into(), Value::Blob(memo.into())),
    ]));
    BlocksReply {
        log_length: 9_u8.into(),
        blocks: vec![CyclesLedgerBlock {
            id: 8_u8.into(),
            block: Value::Map(BTreeMap::from([
                ("phash".into(), Value::Blob(vec![1; 32].into())),
                ("ts".into(), Value::Nat(99_u8.into())),
                ("fee".into(), Value::Nat(10_u8.into())),
                ("tx".into(), tx),
            ])),
        }],
    }
}

fn decode(reply: BlocksReply) -> Result<RetirementWithdrawalRecord, RetirementDebitError> {
    decode_block(
        reply,
        Principal::from_slice(&[2, 1]),
        Principal::self_authenticating(b"operator"),
        8,
        "root-key-hash".into(),
    )
}

#[test]
fn retirement_debit_decodes_exact_external_withdrawal() {
    let record = decode(reply()).unwrap();
    assert_eq!(record.amount_cycles, 1000);
    assert_eq!(record.fee_cycles, 10);
    assert_eq!(record.block_index, 8);
    assert_eq!(record.destination, Principal::from_slice(&[1, 1]).to_text());
    assert_eq!(record.block_sha256.len(), 64);
}

#[test]
fn retirement_debit_rejects_wrong_block_account_and_transaction() {
    for case in 0..11 {
        let mut r = reply();
        match case {
            0 => r.blocks[0].id = 7_u8.into(),
            1 => r.blocks.clear(),
            2 => r.blocks.push(r.blocks[0].clone()),
            3 => r.log_length = 8_u8.into(),
            _ => {
                let Value::Map(fields) = &mut r.blocks[0].block else {
                    unreachable!()
                };
                if case == 4 {
                    fields.insert("fee".into(), Value::Nat(0_u8.into()));
                } else {
                    let Some(Value::Map(tx)) = fields.get_mut("tx") else {
                        unreachable!()
                    };
                    match case {
                        5 => {
                            tx.insert("op".into(), Value::Text("mint".into()));
                        }
                        6 => {
                            tx.insert(
                                "from".into(),
                                Value::Array(vec![Value::Blob(vec![1].into())]),
                            );
                        }
                        7 => {
                            tx.insert("spender".into(), Value::Array(vec![]));
                        }
                        8 => {
                            tx.insert("amt".into(), Value::Nat(u128::MAX.into()));
                        }
                        9 => {
                            tx.insert("memo".into(), Value::Blob(vec![0x81, 0x41, 1, 0].into()));
                        }
                        10 => {
                            tx.insert("memo".into(), Value::Blob(vec![0; 33].into()));
                        }
                        _ => unreachable!(),
                    }
                }
            }
        }
        assert!(decode(r).is_err(), "case {case}");
    }
}