Skip to content

Implement AccountStorageInterface to abstract over account storage #2623

@PhilippGackstatter

Description

@PhilippGackstatter

As it came up in #2456, we need a way for components to read from an account's storage, no matter what form it comes in, i.e. AccountStorage, PartialStorage or AccountReader (defined in the client).

The general goal is:

impl NetworkFungibleFaucet {
	pub async fn try_from_interface(
		interface: AccountInterface,
		storage: &impl AccountStorageInterface,
	) -> Result<Self, AccountError> {
		todo!("interface compatibility check; see https://github.com/0xMiden/protocol/issues/2621");
		
        let metadata = TokenMetadata::try_from_storage(storage).await?;

        Ok(Self { metadata })
	}
}

For this, the easiest way is to add a trait that abstracts over account storage, including the AccountReader which is async. Consequently, the trait methods must be async as well. I don't think this is problem, at least in the protocol repo we don't need to reconstruct components in sync code from what I can tell.

We would have to remove impl TryFrom<&Account> for BasicFungibleFaucet and similar wrappers, but these could be replaced with direct calls to try_from_interface.

The trait could look like this:

pub trait AccountStorageInterface {
    fn get_item(
        &self,
        slot_name: &StorageSlotName,
    ) -> impl FutureMaybeSend<Result<Word, AccountError>>;

    fn get_map_item(
        &self,
        slot_name: &StorageSlotName,
        key: StorageMapKey,
    ) -> impl FutureMaybeSend<Result<Word, AccountError>>;
}

impl AccountStorageInterface for AccountStorage {
    fn get_item(
        &self,
        slot_name: &StorageSlotName,
    ) -> impl FutureMaybeSend<Result<Word, AccountError>> {
        future::ready(self.get_item(slot_name))
    }

    fn get_map_item(
        &self,
        slot_name: &StorageSlotName,
        key: StorageMapKey,
    ) -> impl FutureMaybeSend<Result<Word, AccountError>> {
        future::ready(self.get_map_item(slot_name, key.as_word()))
    }
}

impl AccountStorageInterface for PartialStorage {
    fn get_item(
        &self,
        slot_name: &StorageSlotName,
    ) -> impl FutureMaybeSend<Result<Word, AccountError>> {
        // TODO: Implement PartialStorage::get_item for convenience
        future::ready(Ok(self
            .header()
            .find_slot_header_by_name(slot_name)
            .map(|header| header.value())
            .unwrap_or_default()))
    }

    fn get_map_item(
        &self,
        _slot_name: &StorageSlotName,
        _key: StorageMapKey,
    ) -> impl FutureMaybeSend<Result<Word, AccountError>> {
        async { todo!("implement PartialStorage::get_map_item") }
    }
}

impl TokenMetadata {
    pub async fn try_from_storage(
        storage: &impl AccountStorageInterface,
    ) -> Result<Self, FungibleFaucetError> {
        let metadata_word =
            storage.get_item(TokenMetadata::metadata_slot()).await.map_err(|err| {
                FungibleFaucetError::StorageLookupFailed {
                    slot_name: TokenMetadata::metadata_slot().clone(),
                    source: err,
                }
            })?;

        TokenMetadata::try_from(metadata_word)
    }
}

I'm not sure if we need FutureMaybeSend here, but I suspect if we want to use this in wasm, we do, while we also want Send bounds for non-wasm targets, though I'm not sure about the end users of this.

@igamigo could you check for compatibility with AccountReader? Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    rustIssues that affect or pull requests that update Rust code

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions