nucleus-sdk 0.1.0

Rust SDK for Nucleus vault management
Documentation
use serde_json::Value;
use ethers::{
    abi::{Token},
    types::{Address, Bytes}
};
use std::str::FromStr;
use crate::error::{Result, InvalidInputsError};
use std::collections::HashMap;
use ethers_core::abi::HumanReadableParser;

pub fn encode_with_signature(signature: &str, params: &Vec<Token>) -> Result<Bytes> {
    // Ensure the function signature starts with "function ".
    let full_signature = if signature.trim_start().starts_with("function ") {
        signature.trim().to_owned()
    } else {
        format!("function {}", signature.trim())
    };

    // Parse the human-readable function signature into a Function object.
    let fun = HumanReadableParser::parse_function(&full_signature)
        .map_err(|e| InvalidInputsError(e.to_string()))?;

    // Encode the function call using the provided tokens.
    let encoded = fun
        .encode_input(params)
        .map_err(|e| InvalidInputsError(e.to_string()))?;

    Ok(encoded.into())
}

pub fn checksum_addresses_in_json(data: HashMap<String, Value>) -> Result<HashMap<String, Value>> {
    let mut new_map = HashMap::new();
    for (key, value) in data {
        new_map.insert(key, process_value(value)?);
    }
    Ok(new_map)
}

// Helper function to process individual Values
fn process_value(data: Value) -> Result<Value> {
    match data {
        Value::Object(map) => {
            // Handle dictionary/object
            let mut new_map = serde_json::Map::new();
            for (key, value) in map {
                new_map.insert(key, process_value(value)?);
            }
            Ok(Value::Object(new_map))
        }
        Value::Array(arr) => {
            // Handle array/list
            let new_arr: Result<Vec<Value>> = arr
                .into_iter()
                .map(process_value)
                .collect();
            Ok(Value::Array(new_arr?))
        }
        Value::String(s) => {
            // Handle potential Ethereum address
            if let Ok(addr) = s.parse::<Address>() {
                // Convert to checksum address (without chain_id)
                Ok(Value::String(ethers::utils::to_checksum(&addr, None)))
            } else {
                // If not an address, return original string
                Ok(Value::String(s))
            }
        }
        // Return all other types unchanged
        _ => Ok(data),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;
    
    #[test]
    fn test_encode_function_signature(){
        // Prepare the parameters
        let spender = Token::Address(Address::from_str("0x1234567890abcdef1234567890abcdef12345678").unwrap());
        let amount = Token::Uint(ethers::types::U256::from(32));
        let array = Token::Array(vec![Token::Address(Address::from_str("0x1234567890abcdef1234567890abcdef12345678").unwrap()), Token::Address(Address::from_str("0x1234567890abcdef1234567890abcdef12345678").unwrap())]);
        let struct_token = Token::Tuple(vec![
            Token::Uint(ethers::types::U256::from(32)),
            array.clone()
        ]);
        // Encode the function call data
        let tokens = vec![spender, amount, array, struct_token];
        let calldata = encode_with_signature("approve(address,uint256,address[],(uint256,address[]))", &tokens).unwrap();

        println!("Encoded: 0x{}", hex::encode(&calldata));
        assert!(hex::encode(&calldata) == "f46645f40000000000000000000000001234567890abcdef1234567890abcdef123456780000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000001234567890abcdef1234567890abcdef123456780000000000000000000000001234567890abcdef1234567890abcdef123456780000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000001234567890abcdef1234567890abcdef123456780000000000000000000000001234567890abcdef1234567890abcdef12345678");
    }

    #[test]
    fn test_checksum_addresses() {
        let mut input = HashMap::new();
        let mut inner_map = HashMap::new();
        
        // Create nested structure using HashMap
        inner_map.insert("address".to_string(), json!("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"));
        inner_map.insert("other".to_string(), json!("not an address"));
        
        let mut nested_map = HashMap::new();
        nested_map.insert("address".to_string(), json!("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"));
        inner_map.insert("nested".to_string(), json!(nested_map));
        
        inner_map.insert("array".to_string(), 
            json!(["0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "not an address"]));
        
        input.insert("test_key".to_string(), Value::Object(inner_map.into_iter().collect()));

        let result = checksum_addresses_in_json(input).unwrap();

        // Verify the checksummed addresses
        let result_value = &result["test_key"];
        if let Value::Object(map) = result_value {
            let addr = map["address"].as_str().unwrap();
            assert!(addr.contains(char::is_uppercase), "Address should be checksummed");
            assert_eq!(addr, "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
            
            // Verify nested address is also checksummed
            if let Value::Object(nested) = &map["nested"] {
                let nested_addr = nested["address"].as_str().unwrap();
                assert!(nested_addr.contains(char::is_uppercase), "Nested address should be checksummed");
                assert_eq!(nested_addr, "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
            }
            
            // Verify array address is checksummed
            if let Value::Array(arr) = &map["array"] {
                let arr_addr = arr[0].as_str().unwrap();
                assert!(arr_addr.contains(char::is_uppercase), "Array address should be checksummed");
                assert_eq!(arr_addr, "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
            }
        } else {
            panic!("Expected object");
        }
    }

}