espresso_types/v0/impls/
fee_info.rs

1use std::str::FromStr;
2
3use alloy::primitives::{
4    utils::{parse_units, ParseUnits},
5    Address, U256,
6};
7use anyhow::{bail, ensure, Context};
8use ark_serialize::{
9    CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate,
10};
11use committable::{Commitment, Committable, RawCommitmentBuilder};
12use hotshot_contract_adapter::sol_types::Deposit;
13use hotshot_query_service::explorer::MonetaryValue;
14use hotshot_types::traits::block_contents::BuilderFee;
15use itertools::Itertools;
16use jf_merkle_tree::{
17    ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme, LookupResult, MerkleTreeError,
18    MerkleTreeScheme, ToTraversalPath, UniversalMerkleTreeScheme,
19};
20use num_traits::CheckedSub;
21use sequencer_utils::{
22    impl_serde_from_string_or_integer, impl_to_fixed_bytes, ser::FromStringOrInteger,
23};
24use thiserror::Error;
25
26use super::v0_1::IterableFeeInfo;
27use crate::{
28    eth_signature_key::EthKeyPair, AccountQueryData, FeeAccount, FeeAccountProof, FeeAmount,
29    FeeInfo, FeeMerkleCommitment, FeeMerkleProof, FeeMerkleTree, SeqTypes,
30};
31
32/// Possible charge fee failures
33#[derive(Error, Debug, Eq, PartialEq)]
34pub enum FeeError {
35    #[error("Insuficcient Funds: have {balance:?}, required {amount:?}")]
36    InsufficientFunds {
37        balance: Option<FeeAmount>,
38        amount: FeeAmount,
39    },
40    #[error("Merkle Tree Error: {0}")]
41    MerkleTreeError(MerkleTreeError),
42}
43
44impl FeeInfo {
45    pub fn new(account: impl Into<FeeAccount>, amount: impl Into<FeeAmount>) -> Self {
46        Self {
47            account: account.into(),
48            amount: amount.into(),
49        }
50    }
51    /// The minimum fee paid by the given builder account for a proposed block.
52    // TODO this function should take the block size as an input, we need to get this information
53    // from HotShot.
54    pub fn base_fee(account: FeeAccount) -> Self {
55        Self {
56            account,
57            amount: FeeAmount::default(),
58        }
59    }
60
61    pub fn genesis() -> Self {
62        Self {
63            account: Default::default(),
64            amount: Default::default(),
65        }
66    }
67
68    pub fn account(&self) -> FeeAccount {
69        self.account
70    }
71
72    pub fn amount(&self) -> FeeAmount {
73        self.amount
74    }
75    /// Get a `Vec<FeeInfo>` from `Vec<BuilderFee>`
76    pub fn from_builder_fees(fees: Vec<BuilderFee<SeqTypes>>) -> Vec<FeeInfo> {
77        fees.into_iter().map(FeeInfo::from).collect()
78    }
79}
80
81impl IterableFeeInfo for Vec<FeeInfo> {
82    /// Get sum of fee amounts
83    fn amount(&self) -> Option<FeeAmount> {
84        self.iter()
85            // getting the u64 tests that the value fits
86            .map(|fee_info| fee_info.amount.as_u64())
87            .collect::<Option<Vec<u64>>>()
88            .and_then(|amounts| amounts.iter().try_fold(0u64, |acc, n| acc.checked_add(*n)))
89            .map(FeeAmount::from)
90    }
91
92    /// Get a `Vec` of all unique fee accounts
93    fn accounts(&self) -> Vec<FeeAccount> {
94        self.iter()
95            .unique_by(|entry| &entry.account)
96            .map(|entry| entry.account)
97            .collect()
98    }
99}
100
101impl IterableFeeInfo for Vec<BuilderFee<SeqTypes>> {
102    /// Get sum of amounts
103    fn amount(&self) -> Option<FeeAmount> {
104        self.iter()
105            .map(|fee_info| fee_info.fee_amount)
106            .try_fold(0u64, |acc, n| acc.checked_add(n))
107            .map(FeeAmount::from)
108    }
109
110    /// Get a `Vec` of all unique fee accounts
111    fn accounts(&self) -> Vec<FeeAccount> {
112        self.iter()
113            .unique_by(|entry| &entry.fee_account)
114            .map(|entry| entry.fee_account)
115            .collect()
116    }
117}
118
119impl From<BuilderFee<SeqTypes>> for FeeInfo {
120    fn from(fee: BuilderFee<SeqTypes>) -> Self {
121        Self {
122            amount: fee.fee_amount.into(),
123            account: fee.fee_account,
124        }
125    }
126}
127
128impl From<Deposit> for FeeInfo {
129    fn from(item: Deposit) -> Self {
130        Self {
131            amount: item.amount.into(),
132            account: item.user.into(),
133        }
134    }
135}
136
137impl Committable for FeeInfo {
138    fn commit(&self) -> Commitment<Self> {
139        RawCommitmentBuilder::new(&Self::tag())
140            .fixed_size_field("account", &self.account.to_fixed_bytes())
141            .fixed_size_field("amount", &self.amount.to_fixed_bytes())
142            .finalize()
143    }
144    fn tag() -> String {
145        "FEE_INFO".into()
146    }
147}
148
149impl_serde_from_string_or_integer!(FeeAmount);
150impl_to_fixed_bytes!(FeeAmount, U256);
151
152impl From<u64> for FeeAmount {
153    fn from(amt: u64) -> Self {
154        Self(U256::from(amt))
155    }
156}
157
158impl From<FeeAmount> for MonetaryValue {
159    fn from(value: FeeAmount) -> Self {
160        MonetaryValue::eth(value.0.to::<u128>() as i128)
161    }
162}
163
164impl CheckedSub for FeeAmount {
165    fn checked_sub(&self, v: &Self) -> Option<Self> {
166        self.0.checked_sub(v.0).map(FeeAmount)
167    }
168}
169
170impl FromStr for FeeAmount {
171    type Err = <U256 as FromStr>::Err;
172
173    fn from_str(s: &str) -> Result<Self, Self::Err> {
174        Ok(Self(s.parse()?))
175    }
176}
177
178impl FromStringOrInteger for FeeAmount {
179    type Binary = ethers_core::types::U256;
180    type Integer = u64;
181
182    fn from_binary(b: Self::Binary) -> anyhow::Result<Self> {
183        Ok(Self(U256::from_limbs(b.0)))
184    }
185
186    fn from_integer(i: Self::Integer) -> anyhow::Result<Self> {
187        Ok(i.into())
188    }
189
190    fn from_string(s: String) -> anyhow::Result<Self> {
191        // For backwards compatibility, we have an ad hoc parser for WEI amounts represented as hex
192        // strings.
193        if let Some(s) = s.strip_prefix("0x") {
194            return Ok(Self(U256::from_str_radix(s, 16)?));
195        }
196
197        // Strip an optional non-numeric suffix, which will be interpreted as a unit.
198        let (base, unit) = s
199            .split_once(char::is_whitespace)
200            .unwrap_or((s.as_str(), "wei"));
201        match parse_units(base, unit)? {
202            ParseUnits::U256(n) => Ok(Self(n)),
203            ParseUnits::I256(_) => bail!("amount cannot be negative"),
204        }
205    }
206
207    fn to_binary(&self) -> anyhow::Result<Self::Binary> {
208        Ok(ethers_core::types::U256(self.0.into_limbs()))
209    }
210
211    fn to_string(&self) -> anyhow::Result<String> {
212        Ok(format!("{self}"))
213    }
214}
215
216impl FeeAmount {
217    pub fn as_u64(&self) -> Option<u64> {
218        if self.0 <= U256::from(u64::MAX) {
219            Some(self.0.to::<u64>())
220        } else {
221            None
222        }
223    }
224}
225impl FeeAccount {
226    /// Return inner `Address`
227    pub fn address(&self) -> Address {
228        self.0
229    }
230    /// Return byte slice representation of inner `Address` type
231    pub fn as_bytes(&self) -> &[u8] {
232        self.0.as_slice()
233    }
234    /// Return array containing underlying bytes of inner `Address` type
235    pub fn to_fixed_bytes(self) -> [u8; 20] {
236        self.0.into_array()
237    }
238    pub fn test_key_pair() -> EthKeyPair {
239        EthKeyPair::from_mnemonic(
240            "test test test test test test test test test test test junk",
241            0u32,
242        )
243        .unwrap()
244    }
245}
246
247impl FromStr for FeeAccount {
248    type Err = anyhow::Error;
249
250    fn from_str(s: &str) -> Result<Self, Self::Err> {
251        Ok(Self(s.parse()?))
252    }
253}
254
255impl Valid for FeeAmount {
256    fn check(&self) -> Result<(), SerializationError> {
257        Ok(())
258    }
259}
260
261impl Valid for FeeAccount {
262    fn check(&self) -> Result<(), SerializationError> {
263        Ok(())
264    }
265}
266
267impl CanonicalSerialize for FeeAmount {
268    fn serialize_with_mode<W: std::io::prelude::Write>(
269        &self,
270        mut writer: W,
271        _compress: Compress,
272    ) -> Result<(), SerializationError> {
273        Ok(writer.write_all(&self.to_fixed_bytes())?)
274    }
275
276    fn serialized_size(&self, _compress: Compress) -> usize {
277        core::mem::size_of::<U256>()
278    }
279}
280impl CanonicalDeserialize for FeeAmount {
281    fn deserialize_with_mode<R: Read>(
282        mut reader: R,
283        _compress: Compress,
284        _validate: Validate,
285    ) -> Result<Self, SerializationError> {
286        let mut bytes = [0u8; core::mem::size_of::<U256>()];
287        reader.read_exact(&mut bytes)?;
288        let value = U256::from_le_slice(&bytes);
289        Ok(Self(value))
290    }
291}
292impl CanonicalSerialize for FeeAccount {
293    fn serialize_with_mode<W: std::io::prelude::Write>(
294        &self,
295        mut writer: W,
296        _compress: Compress,
297    ) -> Result<(), SerializationError> {
298        Ok(writer.write_all(self.0.as_slice())?)
299    }
300
301    fn serialized_size(&self, _compress: Compress) -> usize {
302        core::mem::size_of::<Address>()
303    }
304}
305impl CanonicalDeserialize for FeeAccount {
306    fn deserialize_with_mode<R: Read>(
307        mut reader: R,
308        _compress: Compress,
309        _validate: Validate,
310    ) -> Result<Self, SerializationError> {
311        let mut bytes = [0u8; core::mem::size_of::<Address>()];
312        reader.read_exact(&mut bytes)?;
313        let value = Address::from_slice(&bytes);
314        Ok(Self(value))
315    }
316}
317
318impl ToTraversalPath<256> for FeeAccount {
319    fn to_traversal_path(&self, height: usize) -> Vec<usize> {
320        self.0
321            .as_slice()
322            .iter()
323            .take(height)
324            .map(|i| *i as usize)
325            .collect()
326    }
327}
328
329impl FeeAccountProof {
330    pub fn presence(
331        pos: FeeAccount,
332        proof: <FeeMerkleTree as MerkleTreeScheme>::MembershipProof,
333    ) -> Self {
334        Self {
335            account: pos.into(),
336            proof: FeeMerkleProof::Presence(proof),
337        }
338    }
339
340    pub fn absence(
341        pos: FeeAccount,
342        proof: <FeeMerkleTree as UniversalMerkleTreeScheme>::NonMembershipProof,
343    ) -> Self {
344        Self {
345            account: pos.into(),
346            proof: FeeMerkleProof::Absence(proof),
347        }
348    }
349
350    pub fn prove(tree: &FeeMerkleTree, account: Address) -> Option<(Self, U256)> {
351        match tree.universal_lookup(FeeAccount(account)) {
352            LookupResult::Ok(balance, proof) => Some((
353                Self {
354                    account,
355                    proof: FeeMerkleProof::Presence(proof),
356                },
357                balance.0,
358            )),
359            LookupResult::NotFound(proof) => Some((
360                Self {
361                    account,
362                    proof: FeeMerkleProof::Absence(proof),
363                },
364                U256::ZERO,
365            )),
366            LookupResult::NotInMemory => None,
367        }
368    }
369
370    pub fn verify(&self, comm: &FeeMerkleCommitment) -> anyhow::Result<U256> {
371        match &self.proof {
372            FeeMerkleProof::Presence(proof) => {
373                ensure!(
374                    FeeMerkleTree::verify(comm, FeeAccount(self.account), proof)?.is_ok(),
375                    "invalid proof"
376                );
377                Ok(proof
378                    .elem()
379                    .context("presence proof is missing account balance")?
380                    .0)
381            },
382            FeeMerkleProof::Absence(proof) => {
383                let tree = FeeMerkleTree::from_commitment(comm);
384                ensure!(
385                    FeeMerkleTree::non_membership_verify(
386                        tree.commitment(),
387                        FeeAccount(self.account),
388                        proof
389                    )?,
390                    "invalid proof"
391                );
392                Ok(U256::ZERO)
393            },
394        }
395    }
396
397    pub fn remember(&self, tree: &mut FeeMerkleTree) -> anyhow::Result<()> {
398        match &self.proof {
399            FeeMerkleProof::Presence(proof) => {
400                tree.remember(
401                    FeeAccount(self.account),
402                    proof
403                        .elem()
404                        .context("presence proof is missing account balance")?,
405                    proof,
406                )?;
407                Ok(())
408            },
409            FeeMerkleProof::Absence(proof) => {
410                tree.non_membership_remember(FeeAccount(self.account), proof)?;
411                Ok(())
412            },
413        }
414    }
415}
416
417impl From<(FeeAccountProof, U256)> for AccountQueryData {
418    fn from((proof, balance): (FeeAccountProof, U256)) -> Self {
419        Self { balance, proof }
420    }
421}
422
423/// Get a partial snapshot of the given fee state, which contains only the specified accounts.
424///
425/// Fails if one of the requested accounts is not represented in the original `state`.
426pub fn retain_accounts(
427    state: &FeeMerkleTree,
428    accounts: impl IntoIterator<Item = FeeAccount>,
429) -> anyhow::Result<FeeMerkleTree> {
430    let mut snapshot = FeeMerkleTree::from_commitment(state.commitment());
431    for account in accounts {
432        match state.universal_lookup(account) {
433            LookupResult::Ok(elem, proof) => {
434                // This remember cannot fail, since we just constructed a valid proof, and are
435                // remembering into a tree with the same commitment.
436                snapshot.remember(account, *elem, proof).unwrap();
437            },
438            LookupResult::NotFound(proof) => {
439                // Likewise this cannot fail.
440                snapshot.non_membership_remember(account, proof).unwrap()
441            },
442            LookupResult::NotInMemory => {
443                bail!("missing account {account}");
444            },
445        }
446    }
447
448    Ok(snapshot)
449}
450
451#[cfg(test)]
452mod test {
453    use super::{Address, IterableFeeInfo};
454    use crate::{FeeAccount, FeeAmount, FeeInfo};
455
456    #[test]
457    fn test_iterable_fee_info() {
458        let addr = Address::default();
459        let fee = FeeInfo::new(addr, FeeAmount::from(1));
460        let fees = vec![fee, fee, fee];
461        // check the sum of amounts
462        let sum = fees.amount().unwrap();
463        assert_eq!(FeeAmount::from(3), sum);
464
465        // check accounts collector
466        let accounts = fees.accounts();
467        assert_eq!(vec![FeeAccount::from(Address::default())], accounts);
468    }
469}