tycho_common/
storage.rs

1//! Storage traits used by Tycho
2use std::{collections::HashMap, fmt::Display};
3
4use async_trait::async_trait;
5use chrono::NaiveDateTime;
6use thiserror::Error;
7
8use crate::{
9    dto,
10    models::{
11        blockchain::{Block, Transaction},
12        contract::{Account, AccountBalance, AccountDelta},
13        protocol::{
14            ComponentBalance, ProtocolComponent, ProtocolComponentState,
15            ProtocolComponentStateDelta, QualityRange,
16        },
17        token::CurrencyToken,
18        Address, BlockHash, Chain, ComponentId, ContractId, ExtractionState, PaginationParams,
19        ProtocolType, TxHash,
20    },
21    Bytes,
22};
23
24/// Identifies a block in storage.
25#[derive(Debug, Clone, PartialEq, Hash, Eq)]
26pub enum BlockIdentifier {
27    /// Identifies the block by its position on a specified chain.
28    ///
29    /// This form of identification has potential risks as it may become
30    /// ambiguous in certain situations. For example, if the block has not been
31    /// finalised, there exists a possibility of forks occurring. As a result,
32    /// the same number could refer to different blocks on different forks.
33    Number((Chain, i64)),
34
35    /// Identifies a block by its hash.
36    ///
37    /// The hash should be unique across multiple chains. Preferred method if
38    /// the block is very recent.
39    Hash(BlockHash),
40
41    /// Latest stored block for the target chain
42    ///
43    /// Returns the block with the highest block number on the target chain.
44    Latest(Chain),
45}
46
47impl Display for BlockIdentifier {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        write!(f, "{self:?}")
50    }
51}
52
53#[derive(Error, Debug, PartialEq)]
54pub enum StorageError {
55    #[error("Could not find {0} with id `{1}`!")]
56    NotFound(String, String),
57    #[error("The entity {0} with id {1} was already present!")]
58    DuplicateEntry(String, String),
59    #[error("Could not find related {0} for {1} with id `{2}`!")]
60    NoRelatedEntity(String, String, String),
61    #[error("DecodeError: {0}")]
62    DecodeError(String),
63    #[error("Unexpected storage error: {0}")]
64    Unexpected(String),
65    #[error("Currently unsupported operation: {0}")]
66    Unsupported(String),
67    #[error("Write cache unexpectedly dropped notification channel!")]
68    WriteCacheGoneAway(),
69    #[error("Invalid block range encountered")]
70    InvalidBlockRange(),
71}
72
73/// Storage methods for chain specific objects.
74///
75/// This trait abstracts the specific implementation details of a blockchain's
76/// entities, allowing the user to add and retrieve blocks and transactions in a
77/// generic way.
78///
79/// For traceability protocol components and contracts changes are linked to
80/// blocks of their respective chain if applicable. This means while indexing we
81/// need to keep a lightweight and cross chain compatible representation of
82/// blocks and transactions in storage.
83///
84/// It's defined generically over two associated types:
85///
86/// * `Block`: represents a block in the blockchain.
87/// * `Transaction`: represents a transaction within a block.
88#[async_trait]
89pub trait ChainGateway {
90    /// Upserts a new block to the blockchain's storage.
91    ///
92    /// Ignores any existing tx, if the new entry has different attributes
93    /// no error is raised and the old entry is kept.
94    ///
95    /// # Parameters
96    /// - `new`: An instance of `Self::Block`, representing the new block to be stored.
97    ///
98    /// # Returns
99    /// - Empty ok result indicates success. Failure might occur if the block is already present.
100    async fn upsert_block(&self, new: &[Block]) -> Result<(), StorageError>;
101    /// Retrieves a block from storage.
102    ///
103    /// # Parameters
104    /// - `id`: Block's unique identifier of type `BlockIdentifier`.
105    ///
106    /// # Returns
107    /// - An Ok result containing the block. Might fail if the block does not exist yet.
108    async fn get_block(&self, id: &BlockIdentifier) -> Result<Block, StorageError>;
109    /// Upserts a transaction to storage.
110    ///
111    /// Ignores any existing tx, if the new entry has different attributes
112    /// no error is raised and the old entry is kept.
113    ///
114    /// # Parameters
115    /// - `new`: An instance of `Self::Transaction`, representing the new transaction to be stored.
116    ///
117    /// # Returns
118    /// - Empty ok result indicates success. Failure might occur if the
119    /// corresponding block does not exists yet, or if the transaction already
120    /// exists.
121    async fn upsert_tx(&self, new: &[Transaction]) -> Result<(), StorageError>;
122
123    /// Tries to retrieve a transaction from the blockchain's storage using its
124    /// hash.
125    ///
126    /// # Parameters
127    /// - `hash`: The byte slice representing the hash of the transaction to be retrieved.
128    ///
129    /// # Returns
130    /// - An Ok result containing the transaction. Might fail if the transaction does not exist yet.
131    async fn get_tx(&self, hash: &TxHash) -> Result<Transaction, StorageError>;
132
133    /// Reverts the blockchain storage to a previous version.
134    ///
135    /// Reverting state signifies deleting database history. Only the main branch will be kept.
136    ///
137    /// Blocks that are greater than the provided block (`to`) are deleted and any versioned rows
138    /// which were invalidated in the deleted blocks are updated to be valid again.
139    ///
140    /// # Parameters
141    /// - `to` The version to revert to. Given a block uses VersionKind::Last behaviour.
142    /// - `db` The database gateway.
143    ///
144    /// # Returns
145    /// - An Ok if the revert is successful, or a `StorageError` if not.
146    async fn revert_state(&self, to: &BlockIdentifier) -> Result<(), StorageError>;
147}
148
149/// Store and retrieve state of Extractors.
150///
151/// Sometimes extractors may wish to persist their state across restart. E.g.
152/// substreams based extractors need to store the cursor, so they can continue
153/// processing where they left off.
154///
155/// Extractors are uniquely identified by a name and the respective chain which
156/// they are indexing.
157#[async_trait]
158pub trait ExtractionStateGateway {
159    /// Retrieves the state of an extractor instance from a storage.
160    ///
161    /// # Parameters
162    /// - `name` A unique name for the extractor instance.
163    /// - `chain` The chain this extractor is indexing.
164    ///
165    /// # Returns
166    /// Ok if the corrsponding state was retrieved successfully, Err in
167    /// case the state was not found.
168    async fn get_state(&self, name: &str, chain: &Chain) -> Result<ExtractionState, StorageError>;
169
170    /// Saves the state of an extractor instance to a storage.
171    ///
172    /// Creates an entry if not present yet, or updates an already existing
173    /// entry.
174    ///
175    /// # Parameters
176    /// - `state` The state of the extractor that needs to be saved.
177    ///
178    /// # Returns
179    /// Ok, if state was stored successfully, Err if the state is not valid.
180    async fn save_state(&self, state: &ExtractionState) -> Result<(), StorageError>;
181}
182
183/// Point in time as either block or timestamp. If a block is chosen it
184/// timestamp attribute is used.
185#[derive(Debug, Clone, PartialEq, Hash, Eq)]
186pub enum BlockOrTimestamp {
187    Block(BlockIdentifier),
188    Timestamp(NaiveDateTime),
189}
190
191// TODO: remove once deprecated chain field is removed from VersionParam
192#[allow(deprecated)]
193impl TryFrom<&dto::VersionParam> for BlockOrTimestamp {
194    type Error = anyhow::Error;
195
196    fn try_from(version: &dto::VersionParam) -> Result<Self, Self::Error> {
197        match (&version.timestamp, &version.block) {
198            (_, Some(block)) => {
199                // If a full block is provided, we prioritize hash over number and chain
200                let block_identifier = match (&block.hash, &block.chain, &block.number) {
201                    (Some(hash), _, _) => BlockIdentifier::Hash(hash.clone()),
202                    (_, Some(chain), Some(number)) => {
203                        BlockIdentifier::Number((Chain::from(*chain), *number))
204                    }
205                    _ => {
206                        return Err(anyhow::format_err!("Insufficient block information".to_owned()))
207                    }
208                };
209                Ok(BlockOrTimestamp::Block(block_identifier))
210            }
211            (Some(timestamp), None) => Ok(BlockOrTimestamp::Timestamp(*timestamp)),
212            (None, None) => {
213                Err(anyhow::format_err!("Missing timestamp or block identifier".to_owned()))
214            }
215        }
216    }
217}
218
219/// References certain states within a single block.
220///
221/// **Note:** Not all methods that take a version will support all version kinds,
222/// the versions here are included for completeness and to document the
223/// retrieval behaviour that is possible with the storage layout. Please refer
224/// to the individual implementation for information about which version kinds
225/// it supports.
226#[derive(Debug, Clone, Default)]
227pub enum VersionKind {
228    /// Represents the final state within a specific block. Essentially, it
229    /// retrieves the state subsequent to the execution of the last transaction
230    /// executed in that block.
231    #[default]
232    Last,
233    /// Represents the initial state of a specific block. In other words,
234    /// it is the state before any transaction has been executed within that block.
235    First,
236    /// Represents a specific transactions indexed position within a block.
237    /// It includes the state after executing the transaction at that index.
238    Index(i64),
239}
240
241/// A version desribes the state of the DB at a exact point in time.
242/// See the module level docs for more information on how versioning works.
243#[derive(Debug, Clone)]
244pub struct Version(pub BlockOrTimestamp, pub VersionKind);
245
246impl Version {
247    pub fn from_block_number(chain: Chain, number: i64) -> Self {
248        Self(BlockOrTimestamp::Block(BlockIdentifier::Number((chain, number))), VersionKind::Last)
249    }
250    pub fn from_ts(ts: NaiveDateTime) -> Self {
251        Self(BlockOrTimestamp::Timestamp(ts), VersionKind::Last)
252    }
253}
254
255// Helper type to retrieve entities with their total retrievable count.
256#[derive(Debug)]
257pub struct WithTotal<T> {
258    pub entity: T,
259    pub total: Option<i64>,
260}
261
262/// Store and retrieve protocol related structs.
263///
264/// This trait defines how to retrieve protocol components, state as well as
265/// tokens from storage.
266#[async_trait]
267pub trait ProtocolGateway {
268    /// Retrieve ProtocolComponent from the db
269    ///
270    /// # Parameters
271    /// - `chain` The chain of the component
272    /// - `system` Allows to optionally filter by system.
273    /// - `id` Allows to optionally filter by id.
274    ///
275    /// # Returns
276    /// Ok, if found else Err
277    async fn get_protocol_components(
278        &self,
279        chain: &Chain,
280        system: Option<String>,
281        ids: Option<&[&str]>,
282        min_tvl: Option<f64>,
283        pagination_params: Option<&PaginationParams>,
284    ) -> Result<WithTotal<Vec<ProtocolComponent>>, StorageError>;
285
286    /// Retrieves owners of tokens
287    ///
288    /// Queries for owners (protocol components) of tokens that have a certain minimum
289    /// balance and returns a maximum aggregate of those in case there are multiple
290    /// owners.
291    ///
292    /// # Parameters
293    /// - `chain` The chain of the component
294    /// - `tokens` The tokens to query for, any component with at least one of these tokens is
295    ///   returned.
296    /// - `min_balance` A minimum balance we expect the component to have on any of the tokens
297    ///   mentioned in `tokens`.
298    async fn get_token_owners(
299        &self,
300        chain: &Chain,
301        tokens: &[Address],
302        min_balance: Option<f64>,
303    ) -> Result<HashMap<Address, (ComponentId, Bytes)>, StorageError>;
304
305    async fn add_protocol_components(&self, new: &[ProtocolComponent]) -> Result<(), StorageError>;
306
307    async fn delete_protocol_components(
308        &self,
309        to_delete: &[ProtocolComponent],
310        block_ts: NaiveDateTime,
311    ) -> Result<(), StorageError>;
312
313    /// Stores new found ProtocolTypes.
314    ///
315    /// # Parameters
316    /// - `new`  The new protocol types.
317    ///
318    /// # Returns
319    /// Ok if stored successfully.
320    async fn add_protocol_types(
321        &self,
322        new_protocol_types: &[ProtocolType],
323    ) -> Result<(), StorageError>;
324
325    /// Retrieve protocol component states
326    ///
327    /// This resource is versioned, the version can be specified by either block
328    /// or timestamp, for off-chain components, a block version will error.
329    ///
330    /// As the state is retained on a transaction basis on blockchain systems, a
331    /// single version may relate to more than one state. In these cases a
332    /// versioned result is returned, if requesting `Version:All` with the
333    /// latest entry being the state at the end of the block and the first entry
334    /// represents the first change to the state within the block.
335    ///
336    /// # Parameters
337    /// - `chain` The chain of the component
338    /// - `system` The protocol system this component belongs to
339    /// - `ids` The external ids of the components e.g. addresses, or the pairs
340    /// - `at` The version at which the state is valid at.
341    async fn get_protocol_states(
342        &self,
343        chain: &Chain,
344        at: Option<Version>,
345        system: Option<String>,
346        ids: Option<&[&str]>,
347        retrieve_balances: bool,
348        pagination_params: Option<&PaginationParams>,
349    ) -> Result<WithTotal<Vec<ProtocolComponentState>>, StorageError>;
350
351    async fn update_protocol_states(
352        &self,
353        new: &[(TxHash, ProtocolComponentStateDelta)],
354    ) -> Result<(), StorageError>;
355
356    /// Retrieves a tokens from storage
357    ///
358    /// # Parameters
359    /// - `chain` The chain this token is implemented on.
360    /// - `address` The address for the token within the chain.
361    ///
362    /// # Returns
363    /// Ok if the results could be retrieved from the storage, else errors.
364    async fn get_tokens(
365        &self,
366        chain: Chain,
367        address: Option<&[&Address]>,
368        quality: QualityRange,
369        traded_n_days_ago: Option<NaiveDateTime>,
370        pagination_params: Option<&PaginationParams>,
371    ) -> Result<WithTotal<Vec<CurrencyToken>>, StorageError>;
372
373    /// Saves multiple component balances to storage.
374    ///
375    /// # Parameters
376    /// - `component_balances` The component balances to insert.
377    /// - `chain` The chain of the component balances to be inserted.
378    /// - `block_ts` The timestamp of the block that the balances are associated with.
379    ///
380    /// # Return
381    /// Ok if all component balances could be inserted, Err if at least one token failed to
382    /// insert.
383    async fn add_component_balances(
384        &self,
385        component_balances: &[ComponentBalance],
386    ) -> Result<(), StorageError>;
387
388    /// Saves multiple tokens to storage.
389    ///
390    /// Inserts token into storage. Tokens and their properties are assumed to
391    /// be immutable.
392    ///
393    /// # Parameters
394    /// - `token` The tokens to insert.
395    ///
396    /// # Return
397    /// Ok if all tokens could be inserted, Err if at least one token failed to
398    /// insert.
399    async fn add_tokens(&self, tokens: &[CurrencyToken]) -> Result<(), StorageError>;
400
401    /// Updates multiple tokens in storage.
402    ///
403    /// Updates token in storage. Will warn if one of the tokens does not exist in the
404    /// database. Currently assumes that token addresses are unique across chains.
405    /// -
406    ///
407    /// # Parameters
408    /// - `token` The tokens to update.
409    ///
410    /// # Return
411    /// Ok if all tokens could be inserted, Err if at least one token failed to
412    /// insert.
413    async fn update_tokens(&self, tokens: &[CurrencyToken]) -> Result<(), StorageError>;
414
415    /// Retrieve protocol state changes
416    ///
417    /// Fetches all state changes that occurred for the given chain
418    ///
419    /// # Parameters
420    /// - `chain` The chain of the component
421    /// - `start_version` The version at which to start looking for changes at.
422    /// - `end_version` The version at which to stop looking for changes.
423    ///
424    /// # Return
425    /// A list of ProtocolStateDeltas containing all state changes, Err if no changes were found.
426    async fn get_protocol_states_delta(
427        &self,
428        chain: &Chain,
429        start_version: Option<&BlockOrTimestamp>,
430        end_version: &BlockOrTimestamp,
431    ) -> Result<Vec<ProtocolComponentStateDelta>, StorageError>;
432
433    /// Retrieve protocol component balance changes
434    ///
435    /// Fetches all balance changes that occurred for the given protocol system
436    ///
437    /// # Parameters
438    /// - `chain` The chain of the component
439    /// - `start_version` The version at which to start looking for changes at.
440    /// - `target_version` The version at which to stop looking for changes.
441    ///
442    /// # Return
443    /// A vec containing ComponentBalance objects for changed components.
444    async fn get_balance_deltas(
445        &self,
446        chain: &Chain,
447        start_version: Option<&BlockOrTimestamp>,
448        target_version: &BlockOrTimestamp,
449    ) -> Result<Vec<ComponentBalance>, StorageError>;
450
451    async fn get_component_balances(
452        &self,
453        chain: &Chain,
454        ids: Option<&[&str]>,
455        version: Option<&Version>,
456    ) -> Result<HashMap<String, HashMap<Bytes, ComponentBalance>>, StorageError>;
457
458    async fn get_token_prices(&self, chain: &Chain) -> Result<HashMap<Bytes, f64>, StorageError>;
459
460    async fn upsert_component_tvl(
461        &self,
462        chain: &Chain,
463        tvl_values: &HashMap<String, f64>,
464    ) -> Result<(), StorageError>;
465
466    /// Retrieve a list of actively supported protocol systems
467    ///
468    /// Fetches the list of protocol systems supported by the Tycho indexing service.
469    ///
470    /// # Parameters
471    /// - `chain` The chain for which to retrieve supported protocol systems.
472    /// - `pagination_params` Optional pagination parameters to control the number of results.
473    ///
474    /// # Return
475    /// A paginated list of supported protocol systems, along with the total count.
476    async fn get_protocol_systems(
477        &self,
478        chain: &Chain,
479        pagination_params: Option<&PaginationParams>,
480    ) -> Result<WithTotal<Vec<String>>, StorageError>;
481
482    /// Retrieve the components total value locked (TVL).
483    ///
484    /// # Parameters
485    /// - `chain` The chain for which to retrieve the total value locked
486    /// - `system` The protocol system for which to retrieve the total value locked
487    /// - `ids` The ids of the components to retrieve the total value locked for
488    /// - `pagination_params` Optional pagination parameters to control the number of results.
489    ///
490    /// # Return
491    /// A result with a map of component ids to their TVL. Err if storage access failed.
492    async fn get_component_tvls(
493        &self,
494        chain: &Chain,
495        system: Option<String>,
496        ids: Option<&[&str]>,
497        pagination_params: Option<&PaginationParams>,
498    ) -> Result<WithTotal<HashMap<String, f64>>, StorageError>;
499}
500
501/// Manage contracts and their state in storage.
502///
503/// Specifies how to retrieve, add and update contracts in storage.
504#[async_trait]
505pub trait ContractStateGateway {
506    /// Get a contracts state from storage
507    ///
508    /// This method retrieves a single contract from the database.
509    ///
510    /// # Parameters
511    /// - `id` The identifier for the contract.
512    /// - `version` Version at which to retrieve state for. None retrieves the latest state.
513    /// - `include_slots`: Flag to determine whether to include slot changes. If set to `true`, it
514    ///   includes storage slot.
515    /// - `db`: Database session reference.
516    async fn get_contract(
517        &self,
518        id: &ContractId,
519        version: Option<&Version>,
520        include_slots: bool,
521    ) -> Result<Account, StorageError>;
522
523    /// Get multiple contracts' states from storage.
524    ///
525    /// This method retrieves balance and code, and optionally storage, of
526    /// multiple contracts in a chain. It can optionally filter by given
527    /// addresses and retrieve state for specific versions.
528    ///
529    /// # Parameters:
530    /// - `chain`: The blockchain where the contracts reside.
531    /// - `addresses`: Filter for specific addresses. If set to `None`, it retrieves all indexed
532    ///   contracts in the chain.
533    /// - `version`: Version at which to retrieve state for. If set to `None`, it retrieves the
534    ///   latest state.
535    /// - `include_slots`: Flag to determine whether to include slot changes. If set to `true`, it
536    ///   includes storage slot.
537    /// - `db`: Database session reference.
538    ///
539    /// # Returns:
540    /// A `Result` with a list of contract states if the operation is
541    /// successful, or a `StorageError` if the operation fails.
542    async fn get_contracts(
543        &self,
544        chain: &Chain,
545        addresses: Option<&[Address]>,
546        version: Option<&Version>,
547        include_slots: bool,
548        pagination_params: Option<&PaginationParams>,
549    ) -> Result<WithTotal<Vec<Account>>, StorageError>;
550
551    /// Inserts a new contract into the database.
552    ///
553    /// If it the creation transaction is known, the contract will have slots, balance and code
554    /// inserted alongside with the new account else it won't.
555    ///
556    /// # Arguments
557    /// - `new`: A reference to the new contract state to be inserted.
558    /// - `db`: Database session reference.
559    ///
560    /// # Returns
561    /// - A Result with Ok if the operation was successful, and an Err containing `StorageError` if
562    ///   there was an issue inserting the contract into the database. E.g. if the contract already
563    ///   existed.
564    async fn upsert_contract(&self, new: &Account) -> Result<(), StorageError>;
565
566    /// Update multiple contracts
567    ///
568    /// Given contract deltas, this method will batch all updates to contracts across a single
569    /// chain.
570    ///
571    /// As changes are versioned by transaction, each changeset needs to be associated with a
572    /// transaction hash. All references transaction are assumed to be already persisted.
573    ///
574    /// # Arguments
575    ///
576    /// - `chain`: The blockchain which the contracts belong to.
577    /// - `new`: A reference to a slice of tuples where each tuple has a transaction hash (`TxHash`)
578    ///   and a reference to the state delta (`&Self::Delta`) for that transaction.
579    /// - `db`: A mutable reference to the connected database where the updated contracts will be
580    ///   stored.
581    ///
582    /// # Returns
583    ///
584    /// A Result with `Ok` if the operation was successful, and an `Err` containing
585    /// `StorageError` if there was an issue updating the contracts in the database. E.g. if a
586    /// transaction can't be located by it's reference or accounts refer to a different chain then
587    /// the one specified.
588    async fn update_contracts(&self, new: &[(TxHash, AccountDelta)]) -> Result<(), StorageError>;
589
590    /// Mark a contract as deleted
591    ///
592    /// Issues a soft delete of the contract.
593    ///
594    /// # Parameters
595    /// - `id` The identifier for the contract.
596    /// - `at_tx` The transaction hash which deleted the contract. This transaction is assumed to be
597    ///   in storage already. None retrieves the latest state.
598    /// - `db` The database handle or connection.
599    ///
600    /// # Returns
601    /// Ok if the deletion was successful, might Err if:
602    ///  - Contract is not present in storage.
603    ///  - Deletion transaction is not present in storage.
604    ///  - Contract was already deleted.
605    async fn delete_contract(&self, id: &ContractId, at_tx: &TxHash) -> Result<(), StorageError>;
606
607    /// Retrieve a account delta between two versions.
608    ///
609    /// Given start version V1 and end version V2, this method will return the
610    /// changes necessary to move from V1 to V2. So if V1 < V2, it will contain
611    /// the changes of all accounts that changed between the two versions with the
612    /// values corresponding to V2. If V2 < V1 then it will contain all the
613    /// slots that changed between the two versions with the values corresponding to V1.
614    ///
615    /// This method is mainly meant to handle reverts, but can also be used to create delta changes
616    /// between two historical version thus providing the basis for creating a backtestable stream
617    /// of messages.
618    ///
619    /// # Parameters
620    ///
621    /// - `chain` The chain for which to generate the delta changes.
622    /// - `start_version` The deltas start version, given a block uses VersionKind::Last behaviour.
623    ///   If None the latest version is assumed.
624    /// - `end_version` The deltas end version, given a block uses VersionKind::Last behaviour.
625    ///
626    /// # Note
627    ///
628    /// A choice to utilize `BlockOrTimestamp` has been made intentionally in
629    /// this scenario as passing a `Version` by user isn't quite logical.
630    /// Support for deltas is limited to the states at the start or end of
631    /// blocks because blockchain reorganization at the transaction level is not
632    /// common.
633    ///
634    /// The decision to use either the beginning or end state of a block is
635    /// automatically determined by the underlying logic. For example, if we are
636    /// tracing back, `VersionKind::First` retrieval mode will be used.
637    /// Conversely, if we're progressing forward, we would apply the
638    /// `VersionKind::Last` semantics.
639    ///
640    /// # Returns
641    /// A map containing the necessary changes to update a state from start_version to end_version.
642    /// Errors if:
643    ///     - The versions can't be located in storage.
644    ///     - There was an error with the database
645    async fn get_accounts_delta(
646        &self,
647        chain: &Chain,
648        start_version: Option<&BlockOrTimestamp>,
649        end_version: &BlockOrTimestamp,
650    ) -> Result<Vec<AccountDelta>, StorageError>;
651
652    /// Saves multiple account balances to storage.
653    ///
654    /// # Parameters
655    /// - `account_balances` The account balances to insert.
656    /// - `chain` The chain of the account balances to be inserted.
657    /// - `block_ts` The timestamp of the block that the balances are associated with.
658    ///
659    /// # Return
660    /// Ok if all account balances could be inserted, Err if at least one token failed to insert.
661    async fn add_account_balances(
662        &self,
663        account_balances: &[AccountBalance],
664    ) -> Result<(), StorageError>;
665
666    /// Retrieve account balances
667    ///
668    /// # Parameters
669    /// - `chain` The chain of the account balances
670    /// - `accounts` The accounts to query for. If set to `None`, it retrieves balances for all
671    ///   indexed
672    ///  accounts in the chain.
673    /// - `version` Version at which to retrieve balances for. If set to `None`, it retrieves the
674    ///   latest balances.
675    async fn get_account_balances(
676        &self,
677        chain: &Chain,
678        accounts: Option<&[Address]>,
679        version: Option<&Version>,
680    ) -> Result<HashMap<Address, HashMap<Address, AccountBalance>>, StorageError>;
681}
682
683pub trait Gateway:
684    ChainGateway
685    + ContractStateGateway
686    + ExtractionStateGateway
687    + ProtocolGateway
688    + ContractStateGateway
689    + Send
690    + Sync
691{
692}