dist_agent_lang 1.0.19

Agentic programming with library and CLI support for Off/On-chain network integration
Documentation
#[cfg(test)]
pub(crate) const SIG_ERROR_STRING: &str = "Error(string)";
#[cfg(test)]
pub(crate) const SIG_PANIC_UINT256: &str = "Panic(uint256)";

#[cfg(test)]
pub(crate) const SIG_ERC20_BALANCE_OF: &str = "balanceOf(address)";
#[cfg(test)]
pub(crate) const SIG_ERC20_TRANSFER: &str = "transfer(address,uint256)";
#[cfg(test)]
pub(crate) const SIG_ERC20_APPROVE: &str = "approve(address,uint256)";
#[cfg(test)]
pub(crate) const SIG_ERC20_TRANSFER_FROM: &str = "transferFrom(address,address,uint256)";
#[cfg(test)]
pub(crate) const SIG_ERC20_ALLOWANCE: &str = "allowance(address,address)";
#[cfg(test)]
pub(crate) const SIG_ERC20_TOTAL_SUPPLY: &str = "totalSupply()";
#[cfg(test)]
pub(crate) const SIG_ERC20_DECIMALS: &str = "decimals()";
#[cfg(test)]
pub(crate) const SIG_ERC721_OWNER_OF: &str = "ownerOf(uint256)";
#[cfg(test)]
pub(crate) const SIG_ERC721_TOKEN_URI: &str = "tokenURI(uint256)";

#[cfg(test)]
pub(crate) const SIG_ERC20_INSUFFICIENT_BALANCE: &str =
    "ERC20InsufficientBalance(address,uint256,uint256)";
#[cfg(test)]
pub(crate) const SIG_ERC20_INVALID_SENDER: &str = "ERC20InvalidSender(address)";
#[cfg(test)]
pub(crate) const SIG_ERC20_INVALID_RECEIVER: &str = "ERC20InvalidReceiver(address)";

#[cfg(test)]
pub(crate) const SELECTOR_ERROR_STRING: &str = "08c379a0";
#[cfg(test)]
pub(crate) const SELECTOR_PANIC_UINT256: &str = "4e487b71";

pub(crate) const SELECTOR_ERC20_BALANCE_OF: &str = "70a08231";
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) const SELECTOR_ERC20_TRANSFER: &str = "a9059cbb";
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) const SELECTOR_ERC20_APPROVE: &str = "095ea7b3";
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) const SELECTOR_ERC20_TRANSFER_FROM: &str = "23b872dd";
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) const SELECTOR_ERC20_ALLOWANCE: &str = "dd62ed3e";
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) const SELECTOR_ERC20_TOTAL_SUPPLY: &str = "18160ddd";
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) const SELECTOR_ERC20_DECIMALS: &str = "313ce567";
pub(crate) const SELECTOR_ERC721_OWNER_OF: &str = "6352211e";
pub(crate) const SELECTOR_ERC721_TOKEN_URI: &str = "c87b56dd";

#[cfg(test)]
pub(crate) const SELECTOR_ERC20_INSUFFICIENT_BALANCE: &str = "e450d38c";
#[cfg(test)]
pub(crate) const SELECTOR_ERC20_INVALID_SENDER: &str = "96c6fd1e";
#[cfg(test)]
pub(crate) const SELECTOR_ERC20_INVALID_RECEIVER: &str = "ec442f05";

#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn selector_from_signature(signature: &str) -> String {
    use sha3::{Digest, Keccak256};

    let mut hasher = Keccak256::new();
    hasher.update(signature.as_bytes());
    let digest = hasher.finalize();
    hex::encode(&digest[..4])
}

#[cfg(test)]
pub(crate) fn canonical_selector_catalog() -> std::collections::HashMap<&'static str, &'static str>
{
    std::collections::HashMap::from([
        (SIG_ERROR_STRING, SELECTOR_ERROR_STRING),
        (SIG_PANIC_UINT256, SELECTOR_PANIC_UINT256),
        ("ERC20.balanceOf(address)", SELECTOR_ERC20_BALANCE_OF),
        ("ERC20.transfer(address,uint256)", SELECTOR_ERC20_TRANSFER),
        ("ERC20.approve(address,uint256)", SELECTOR_ERC20_APPROVE),
        (
            "ERC20.transferFrom(address,address,uint256)",
            SELECTOR_ERC20_TRANSFER_FROM,
        ),
        ("ERC20.allowance(address,address)", SELECTOR_ERC20_ALLOWANCE),
        ("ERC20.totalSupply()", SELECTOR_ERC20_TOTAL_SUPPLY),
        ("ERC20.decimals()", SELECTOR_ERC20_DECIMALS),
        ("ERC721.ownerOf(uint256)", SELECTOR_ERC721_OWNER_OF),
        ("ERC721.tokenURI(uint256)", SELECTOR_ERC721_TOKEN_URI),
        (
            SIG_ERC20_INSUFFICIENT_BALANCE,
            SELECTOR_ERC20_INSUFFICIENT_BALANCE,
        ),
        (SIG_ERC20_INVALID_SENDER, SELECTOR_ERC20_INVALID_SENDER),
        (SIG_ERC20_INVALID_RECEIVER, SELECTOR_ERC20_INVALID_RECEIVER),
    ])
}

#[cfg(test)]
mod tests {
    use super::{
        canonical_selector_catalog, selector_from_signature, SELECTOR_ERC20_ALLOWANCE,
        SELECTOR_ERC20_APPROVE, SELECTOR_ERC20_BALANCE_OF, SELECTOR_ERC20_DECIMALS,
        SELECTOR_ERC20_INSUFFICIENT_BALANCE, SELECTOR_ERC20_INVALID_RECEIVER,
        SELECTOR_ERC20_INVALID_SENDER, SELECTOR_ERC20_TOTAL_SUPPLY, SELECTOR_ERC20_TRANSFER,
        SELECTOR_ERC20_TRANSFER_FROM, SELECTOR_ERC721_OWNER_OF, SELECTOR_ERC721_TOKEN_URI,
        SELECTOR_ERROR_STRING, SELECTOR_PANIC_UINT256, SIG_ERC20_ALLOWANCE, SIG_ERC20_APPROVE,
        SIG_ERC20_BALANCE_OF, SIG_ERC20_DECIMALS, SIG_ERC20_INSUFFICIENT_BALANCE,
        SIG_ERC20_INVALID_RECEIVER, SIG_ERC20_INVALID_SENDER, SIG_ERC20_TOTAL_SUPPLY,
        SIG_ERC20_TRANSFER, SIG_ERC20_TRANSFER_FROM, SIG_ERC721_OWNER_OF, SIG_ERC721_TOKEN_URI,
        SIG_ERROR_STRING, SIG_PANIC_UINT256,
    };

    #[test]
    fn selector_from_signature_matches_known_vectors() {
        assert_eq!(
            selector_from_signature("transfer(address,uint256)"),
            "a9059cbb"
        );
        assert_eq!(
            selector_from_signature("approve(address,uint256)"),
            "095ea7b3"
        );
        assert_eq!(
            selector_from_signature("ERC20InvalidReceiver(address)"),
            "ec442f05"
        );
    }

    #[test]
    fn selector_is_lowercase_8_hex_chars() {
        let selector = selector_from_signature("balanceOf(address)");
        assert_eq!(selector.len(), 8);
        assert!(selector.chars().all(|c| c.is_ascii_hexdigit()));
        assert_eq!(selector, selector.to_ascii_lowercase());
    }

    #[test]
    fn typed_selector_constants_match_keccak_derivation() {
        assert_eq!(
            selector_from_signature(SIG_ERROR_STRING),
            SELECTOR_ERROR_STRING
        );
        assert_eq!(
            selector_from_signature(SIG_PANIC_UINT256),
            SELECTOR_PANIC_UINT256
        );
        assert_eq!(
            selector_from_signature(SIG_ERC20_BALANCE_OF),
            SELECTOR_ERC20_BALANCE_OF
        );
        assert_eq!(
            selector_from_signature(SIG_ERC20_TRANSFER),
            SELECTOR_ERC20_TRANSFER
        );
        assert_eq!(
            selector_from_signature(SIG_ERC20_APPROVE),
            SELECTOR_ERC20_APPROVE
        );
        assert_eq!(
            selector_from_signature(SIG_ERC20_TRANSFER_FROM),
            SELECTOR_ERC20_TRANSFER_FROM
        );
        assert_eq!(
            selector_from_signature(SIG_ERC20_ALLOWANCE),
            SELECTOR_ERC20_ALLOWANCE
        );
        assert_eq!(
            selector_from_signature(SIG_ERC20_TOTAL_SUPPLY),
            SELECTOR_ERC20_TOTAL_SUPPLY
        );
        assert_eq!(
            selector_from_signature(SIG_ERC20_DECIMALS),
            SELECTOR_ERC20_DECIMALS
        );
        assert_eq!(
            selector_from_signature(SIG_ERC721_OWNER_OF),
            SELECTOR_ERC721_OWNER_OF
        );
        assert_eq!(
            selector_from_signature(SIG_ERC721_TOKEN_URI),
            SELECTOR_ERC721_TOKEN_URI
        );
        assert_eq!(
            selector_from_signature(SIG_ERC20_INSUFFICIENT_BALANCE),
            SELECTOR_ERC20_INSUFFICIENT_BALANCE
        );
        assert_eq!(
            selector_from_signature(SIG_ERC20_INVALID_SENDER),
            SELECTOR_ERC20_INVALID_SENDER
        );
        assert_eq!(
            selector_from_signature(SIG_ERC20_INVALID_RECEIVER),
            SELECTOR_ERC20_INVALID_RECEIVER
        );
    }

    #[test]
    fn canonical_selector_catalog_has_unique_values() {
        let catalog = canonical_selector_catalog();
        let mut unique = std::collections::HashSet::new();
        for selector in catalog.values() {
            assert_eq!(selector.len(), 8);
            assert!(selector.chars().all(|c| c.is_ascii_hexdigit()));
            assert_eq!(*selector, selector.to_ascii_lowercase());
            assert!(
                unique.insert(*selector),
                "duplicate selector in canonical catalog: {}",
                selector
            );
        }
        assert_eq!(unique.len(), catalog.len());
    }
}