use fuel_core_chain_config::GenesisCommitment;
use fuel_core_storage::{
not_found,
tables::{
ContractsAssets,
ContractsLatestUtxo,
ContractsState,
},
Mappable,
MerkleRoot,
MerkleRootStorage,
StorageAsRef,
StorageInspect,
};
use fuel_core_types::{
fuel_crypto::Hasher,
fuel_types::{
Bytes32,
ContractId,
},
services::executor::{
Error as ExecutorError,
Result as ExecutorResult,
},
};
use std::{
borrow::Cow,
error::Error as StdError,
};
pub struct ContractRef<Database> {
database: Database,
contract_id: ContractId,
}
impl<Database> ContractRef<Database> {
pub fn new(database: Database, contract_id: ContractId) -> Self {
Self {
database,
contract_id,
}
}
pub fn contract_id(&self) -> &ContractId {
&self.contract_id
}
pub fn database(&self) -> &Database {
&self.database
}
pub fn database_mut(&mut self) -> &mut Database {
&mut self.database
}
}
impl<Database> ContractRef<Database>
where
Database: StorageInspect<ContractsLatestUtxo>,
ExecutorError: From<Database::Error>,
{
pub fn utxo(
&self,
) -> Result<
Option<Cow<'_, <ContractsLatestUtxo as Mappable>::OwnedValue>>,
Database::Error,
> {
self.database.storage().get(&self.contract_id)
}
}
impl<Database> ContractRef<Database>
where
Database: StorageInspect<ContractsLatestUtxo>,
ExecutorError: From<Database::Error>,
{
pub fn validated_utxo(
&self,
utxo_validation: bool,
) -> ExecutorResult<<ContractsLatestUtxo as Mappable>::OwnedValue> {
let maybe_utxo_id = self.utxo()?.map(|utxo| utxo.into_owned());
let expected_utxo_id = if utxo_validation {
maybe_utxo_id.ok_or(ExecutorError::ContractUtxoMissing(self.contract_id))?
} else {
maybe_utxo_id.unwrap_or_default()
};
Ok(expected_utxo_id)
}
}
impl<Database> ContractRef<Database>
where
Database: MerkleRootStorage<ContractId, ContractsAssets>,
{
pub fn balance_root(
&mut self,
) -> Result<Bytes32, <Database as StorageInspect<ContractsAssets>>::Error> {
self.database.root(&self.contract_id).map(Into::into)
}
}
impl<Database> ContractRef<Database>
where
Database: MerkleRootStorage<ContractId, ContractsState>,
{
pub fn state_root(
&mut self,
) -> Result<Bytes32, <Database as StorageInspect<ContractsState>>::Error> {
self.database.root(&self.contract_id).map(Into::into)
}
}
pub trait ContractStorageTrait:
StorageInspect<ContractsLatestUtxo, Error = Self::InnerError>
+ MerkleRootStorage<ContractId, ContractsState, Error = Self::InnerError>
+ MerkleRootStorage<ContractId, ContractsAssets, Error = Self::InnerError>
{
type InnerError: StdError + Send + Sync + 'static;
}
impl<'a, Database> GenesisCommitment for ContractRef<&'a mut Database>
where
Database: ContractStorageTrait,
{
fn root(&self) -> anyhow::Result<MerkleRoot> {
let contract_id = *self.contract_id();
let utxo = self
.database()
.storage::<ContractsLatestUtxo>()
.get(&contract_id)?
.ok_or(not_found!(ContractsLatestUtxo))?
.into_owned();
let state_root = self
.database()
.storage::<ContractsState>()
.root(&contract_id)?;
let balance_root = self
.database()
.storage::<ContractsAssets>()
.root(&contract_id)?;
let contract_hash = *Hasher::default()
.chain(contract_id.as_ref())
.chain(utxo.tx_id().as_ref())
.chain([utxo.output_index()])
.chain(state_root.as_slice())
.chain(balance_root.as_slice())
.finalize();
Ok(contract_hash)
}
}