1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use async_trait::async_trait;
use filament_chain::{Account, Address};
use filament_encoding::{StateReadDecode, StateWriteEncode};

mod state_key {
    use filament_chain::Address;

    use crate::state_key::StateKey as _;

    #[inline]
    pub fn next_account_id() -> String {
        "latest_account_id".to_string()
    }

    pub fn by_address(address: &Address) -> String {
        format!("accounts/{}", address.state_key())
    }
}

#[async_trait]
pub trait AccountsRead: StateReadDecode {
    async fn account(&self, address: &Address) -> eyre::Result<Option<Account>> {
        let key = state_key::by_address(address);
        self.get_bcs::<Account>(&key).await
    }
}

impl<T: StateReadDecode + ?Sized> AccountsRead for T {}

#[async_trait]
pub trait AccountsWrite: StateWriteEncode {
    async fn increment_id(&mut self) -> eyre::Result<u64> {
        let old = self
            .get_bcs::<u64>(&state_key::next_account_id())
            .await?
            .unwrap_or_default();
        let new = old + 1;
        self.put_bcs(state_key::next_account_id(), &new)?;

        Ok(old)
    }

    async fn create_account(&mut self, address: Address) -> eyre::Result<()> {
        let key = state_key::by_address(&address);
        if self.get_bcs::<Account>(&key).await?.is_some() {
            return Err(eyre::eyre!("accout exists already"));
        }

        let id = self.increment_id().await?;
        let key = state_key::by_address(&address);

        self.put_bcs(
            key,
            &Account::Single {
                address,
                id,
                sequence: 0,
            },
        )
    }

    async fn increment_sequence(&mut self, address: &Address) -> eyre::Result<()> {
        let key = state_key::by_address(address);
        let account = match self.get_bcs::<Account>(&key).await? {
            None => return Err(eyre::eyre!("account doesn't exist: {address:?}")),
            Some(account) => account,
        };

        match account {
            Account::Single { id, sequence, .. } => self.put_bcs(
                key,
                &Account::Single {
                    address: address.clone(),
                    id,
                    sequence: sequence + 1,
                },
            ),
        }
    }
}

impl<T: StateWriteEncode + ?Sized> AccountsWrite for T {}

#[cfg(test)]
mod test {
    use cnidarium::{StateDelta, Storage};
    use filament_chain::{Account, Address};
    use filament_crypto::SigningKey;
    use pretty_assertions::assert_eq;
    use rand::thread_rng;
    use tempfile::tempdir;

    use super::AccountsWrite as _;
    use crate::component::accounts::AccountsRead;

    // TODO(xla): Remove once Account has more than one variant.
    #[allow(irrefutable_let_patterns)]
    #[tokio::test]
    async fn create_account() -> eyre::Result<()> {
        let dir = tempdir()?;
        let path = dir.into_path();
        let storage = Storage::load(path.clone(), vec![])
            .await
            .map_err(|e| eyre::eyre!(e))?;

        let signer = SigningKey::new(thread_rng());
        let addr = Address::from(signer.verification_key());

        {
            let mut state = StateDelta::new(storage.latest_snapshot());
            let mut state_tx = StateDelta::new(&mut state);

            // First time should succeed.
            state_tx.create_account(addr.clone()).await?;

            // Subsequent creations for the same address should fail.
            assert!(state_tx.create_account(addr.clone()).await.is_err());

            state_tx.apply();

            storage.commit(state).await.unwrap();
        }

        // Account should be present in the state.
        let state = StateDelta::new(storage.latest_snapshot());
        let account = state.account(&addr).await?.unwrap();

        if let Account::Single {
            address,
            id,
            sequence,
        } = account
        {
            assert_eq!(address, addr);
            assert_eq!(id, 0);
            assert_eq!(sequence, 0);
        } else {
            panic!("expected single account in state");
        }

        Ok(())
    }
}