ant-act 0.0.5

Autonomi Community Token
Documentation
use autonomi::{
    client::payment::PaymentOption, Bytes, Client, PublicKey, ScratchpadAddress, SecretKey, XorName,
};
use futures::Future;
use ruint::aliases::U256;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub struct Wallet(
    PublicKey,
    HashMap<Option<XorName>, (U256, Vec<(PublicKey, U256)>)>,
    U256,
);
// TODO: ? give index key a name
// TODO: ? optional pubkey ("none" meaning waiting for payment)? this would require supplying amount arg for request.
// TODO: read/write wallet to autonomi: serde.
// TODO: read/write wallet to bytes: serde.

impl Wallet {
    pub fn new(pk: PublicKey) -> Self {
        Self(pk, HashMap::new(), U256::ZERO)
    }

    /// If you're creating a token, `token_id` can be `None`.
    pub fn request(&mut self, req_token_id: Option<XorName>) -> Result<PublicKey, String> {
        println!("request token_id: {:?}", req_token_id);
        let index = self.1.get(&req_token_id).map(|(index, _spends)| index);
        println!("found index: {:?}", index);

        let index = match index {
            Some(index) => index,
            None => {
                self.2 = self
                    .2
                    .checked_add(U256::from(1)) // increment
                    .ok_or("This wallet is full".to_string())?;
                self.1.insert(req_token_id, (self.2, Vec::new()));
                &self.2
            }
        };
        println!("request index: {}", index);

        let request_key = self.0.derive_child(&index.to_be_bytes::<32>());
        println!(
            "request Derived PublicKey: {:.4}(...), {:?}",
            request_key.to_hex(),
            request_key
        );

        Ok(request_key)
    }

    pub fn receive(
        &mut self,
        amount: U256,
        received_token_id: XorName,
        spend: PublicKey,
    ) -> Result<(), String> {
        let entry = self.1.get(&Some(received_token_id));

        match entry {
            None => {
                let none_entry = self.1.remove(&None);
                match none_entry {
                    Some(ne) => {
                        // receiveing tokens, which token_id was not known when requesting.
                        self.1.insert(Some(received_token_id), ne.clone());
                    }
                    None => (),
                }
            }
            Some(_) => (),
        };

        let entry = self.1.get_mut(&Some(received_token_id));

        match entry {
            Some((_index, spends)) => {
                spends.push((spend, amount));
                Some(())
            }
            None => {
                return Err("No requested key in this wallet.".into());
            }
        };

        Ok(())
    }

    pub fn balance_total(&self) -> HashMap<XorName, Result<U256, String>> {
        self.1.iter().fold(
            HashMap::<XorName, Result<U256, String>>::new(),
            |mut token_balances, (token_id, (_index, spends))| {
                if let Some(id) = *token_id {
                    token_balances.insert(
                        id,
                        spends
                            .iter()
                            .fold(Ok(U256::ZERO), |sum_res, (_spend, amount)| {
                                sum_res.and_then(|sum| match sum.overflowing_add(*amount) {
                                    (added, false) => Ok(added),
                                    (_, true) => Err("Overflow.".into()),
                                })
                            }),
                    );
                }

                token_balances
            },
        )
    }

    pub fn balance(&self, token_id: XorName) -> Result<U256, String> {
        let spends = match self.1.get(&Some(token_id)) {
            None => {
                return Ok(U256::ZERO);
            }
            Some((_index, spends)) => spends,
        };

        let (balance, overflow) = spends.iter().fold(
            (U256::ZERO, false),
            |(sum, any_overflow), (_spend, amount)| {
                let (added, this_overflow) = sum.overflowing_add(*amount);
                (added, any_overflow || this_overflow)
            },
        );

        match overflow {
            false => Ok(balance),
            true => Err("Overflow.".into()),
        }
    }

    pub fn take_to_spend(
        &mut self,
        token_id: XorName,
    ) -> Result<(Vec<PublicKey>, U256, PublicKey), String> {
        let (spends, sum, overflow) = self
            .1
            .remove(&Some(token_id))
            .map(|(_index, spends)| {
                let (sum, overflow) = spends.iter().fold(
                    (U256::ZERO, false),
                    |(sum, any_overflow), (spend, amount)| {
                        println!("Input: {:?}", (token_id, spend, amount));
                        let (sum, this_overflow) = sum.overflowing_add(*amount);
                        (sum, any_overflow || this_overflow)
                    },
                );
                (
                    spends.iter().map(|(spend, _amount)| *spend).collect(),
                    sum,
                    overflow,
                )
            })
            .unwrap_or((Vec::new(), U256::ZERO, false));

        match overflow {
            false => self
                .request(Some(token_id))
                .map(|rest_key| (spends, sum, rest_key)),
            true => Err("Overflow.".into()),
        }
    }

    pub fn index_of_token(&self, token_id: XorName) -> Option<U256> {
        self.1.get(&Some(token_id)).map(|(index, _spends)| *index)
    }

    pub fn pk_of_token(&self, token_id: XorName) -> Option<PublicKey> {
        self.index_of_token(token_id)
            .map(|index| self.0.derive_child(&index.to_be_bytes::<32>()))
    }

    pub fn index_that_derives(&self, request: PublicKey) -> Option<U256> {
        self.1
            .iter()
            .filter(|(_, (index, _))| self.0.derive_child(&index.to_be_bytes::<32>()) == request)
            .map(|(_, (index, _))| *index)
            .collect::<Vec<_>>()
            .first()
            .copied()
    }

    pub fn received_spend(&self, token_id: XorName, spend: PublicKey) -> bool {
        self.1
            .get(&Some(token_id))
            .and_then(|(_index, spends)| {
                match spends
                    .iter()
                    .filter(|(received_spend, _amount)| received_spend == &spend)
                    .count()
                {
                    0 => None,
                    _ => Some(()),
                }
            })
            .is_some()
    }
}

pub trait WalletExt {
    fn act_wallet_get(
        &self,
        sk: &SecretKey,
    ) -> impl Future<Output = Result<Option<Wallet>, String>> + Send;

    fn act_wallet_save(
        &mut self,
        wallet: &Wallet,
        sk: &SecretKey,
        payment: &PaymentOption,
    ) -> impl Future<Output = Result<PublicKey, String>> + Send;
}

impl WalletExt for Client {
    async fn act_wallet_get(&self, sk: &SecretKey) -> Result<Option<Wallet>, String> {
        let address = ScratchpadAddress::new(sk.public_key());

        if !self
            .scratchpad_check_existence(&address)
            .await
            .map_err(|e| format!("{e}"))?
        {
            return Ok(None);
        }

        match self.scratchpad_get(&address).await {
            Ok(sp) => {
                let bytes = sp.decrypt_data(sk).map_err(|e| format!("{e}"))?;
                let wallet = rmp_serde::from_slice(&bytes).map_err(|e| format!("{e}"))?;
                Ok(Some(wallet))
            }
            Err(e) => Err(format!("{e}")),
        }
    }

    async fn act_wallet_save(
        &mut self,
        wallet: &Wallet,
        sk: &SecretKey,
        payment: &PaymentOption,
    ) -> Result<PublicKey, String> {
        println!("saving: {:?}", wallet);
        println!("sk: {:.4}(...)", sk.to_hex());
        let data = rmp_serde::to_vec(&wallet).map_err(|e| format!("{e}"))?;

        let existing = self.act_wallet_get(sk).await?;

        match existing {
            Some(_) => self
                .scratchpad_update(sk, 0, &Bytes::from(data))
                .await
                .map(|_| sk.public_key()),
            None => self
                .scratchpad_create(sk, 0, &Bytes::from(data), payment.clone())
                .await
                .map(|(_paid, address)| *address.owner()),
        }
        .map_err(|e| format!("{e}"))
    }
}

#[cfg(test)]
mod tests {

    #[test]
    fn de_ser_ialize() -> Result<(), String> {
        use super::*;

        let mut w = Wallet::new(
            SecretKey::from_hex("4f7eedb7b093537a4402daa0769dfca018520ee3ea2107338d89cbfcc312451b")
                .map_err(|e| format!("{e}"))?
                .public_key(),
        );

        let token_id = XorName::from_content(&[123u8, 45u8]);
        let spend_address = PublicKey::from_hex("a625836b8970244eae677e6338145fe90d9789777a47311d13af4141c16efdeba60d60db86d7972e86e6d17e8b4db0af").map_err(|e| format!("{e}"))?;

        w.request(Some(token_id))?;
        w.receive(U256::from(1), token_id, spend_address)?;

        let data = Bytes::from(rmp_serde::to_vec(&w).map_err(|e| format!("{e}"))?);

        println!("{:x}", data);

        assert_eq!(
			format!("{:x}", data),
			"93dc0030cc876006073c4eccf1cc9d23ccdfcc94cc8fccea3e7ecc9539cc8d6a7b5acccc6a510f24cce2ccd5160bccbbccd9ccf636cca5cc8cccdfcce6cc9ccc8eccba42ccb1cccfccce0fcca60a81dc0020cca4ccfe1bccc8cca631ccbe22ccaecc96ccad524b13ccf64d68ccefccc503cced40cc86ccd6ccaf4ecca906ccc915cce8ccf492c42000000000000000000000000000000000000000000000000000000000000000019192dc0030cca625cc836bcc8970244eccae677e6338145fcce90dcc97cc89777a47311d13ccaf4141ccc16eccfdccebcca60d60ccdbcc86ccd7cc972ecc86cce6ccd17ecc8b4dccb0ccafc4200000000000000000000000000000000000000000000000000000000000000001c4200000000000000000000000000000000000000000000000000000000000000001".to_string()
		);

        let w2 = rmp_serde::from_slice::<Wallet>(&data).map_err(|e| format!("{e}"))?;

        assert_eq!(w, w2);

        Ok(())
    }

    #[test]
    fn received_spend() -> Result<(), String> {
        use super::*;

        let mut w = Wallet::new(SecretKey::random().public_key());
        println!("{w:?}");

        let token_id = XorName::from_content(&[0u8]);
        let spend_address = SecretKey::random().public_key();

        assert_eq!(false, w.received_spend(token_id, spend_address));

        w.request(Some(token_id))?;
        println!("{w:?}");
        w.receive(U256::from(1), token_id, spend_address)?;
        println!("{w:?}");

        assert_eq!(true, w.received_spend(token_id, spend_address));

        Ok(())
    }
}