use std::{default::Default, fmt::Debug, path::Path};
use fuel_tx::{StorageSlot, TxId};
use fuels_accounts::Account;
use fuels_core::{
Configurables,
constants::WORD_SIZE,
error,
types::{
Bytes32, ContractId, Salt,
errors::{Context, Result},
transaction::{Transaction, TxPolicies},
transaction_builders::{Blob, CreateTransactionBuilder},
tx_status::Success,
},
};
use super::{
BlobsNotUploaded, Contract, Loader, StorageConfiguration, compute_contract_id_and_state_root,
validate_path_and_extension,
};
use crate::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE;
#[derive(Clone, Debug)]
pub struct DeployResponse {
pub tx_status: Option<Success>,
pub tx_id: Option<TxId>,
pub contract_id: ContractId,
}
mod code_types {
use fuels_core::Configurables;
#[derive(Debug, Clone, PartialEq)]
pub struct Regular {
code: Vec<u8>,
configurables: Configurables,
}
impl Regular {
pub(crate) fn new(code: Vec<u8>, configurables: Configurables) -> Self {
Self {
code,
configurables,
}
}
pub(crate) fn with_code(self, code: Vec<u8>) -> Self {
Self { code, ..self }
}
pub(crate) fn with_configurables(self, configurables: Configurables) -> Self {
Self {
configurables,
..self
}
}
pub(crate) fn code(&self) -> Vec<u8> {
let mut code = self.code.clone();
self.configurables.update_constants_in(&mut code);
code
}
}
}
pub use code_types::*;
impl Contract<Regular> {
pub fn with_code(self, code: Vec<u8>) -> Self {
Self {
code: self.code.with_code(code),
salt: self.salt,
storage_slots: self.storage_slots,
}
}
pub fn with_configurables(self, configurables: impl Into<Configurables>) -> Self {
Self {
code: self.code.with_configurables(configurables.into()),
..self
}
}
pub fn code(&self) -> Vec<u8> {
self.code.code()
}
pub fn contract_id(&self) -> ContractId {
self.compute_roots().0
}
pub fn code_root(&self) -> Bytes32 {
self.compute_roots().1
}
pub fn state_root(&self) -> Bytes32 {
self.compute_roots().2
}
fn compute_roots(&self) -> (ContractId, Bytes32, Bytes32) {
compute_contract_id_and_state_root(&self.code(), &self.salt, &self.storage_slots)
}
pub fn load_from(
binary_filepath: impl AsRef<Path>,
config: LoadConfiguration,
) -> Result<Contract<Regular>> {
let binary_filepath = binary_filepath.as_ref();
validate_path_and_extension(binary_filepath, "bin")?;
let binary = std::fs::read(binary_filepath).map_err(|e| {
std::io::Error::new(
e.kind(),
format!("failed to read binary: {binary_filepath:?}: {e}"),
)
})?;
let storage_slots = super::determine_storage_slots(config.storage, binary_filepath)?;
Ok(Contract {
code: Regular::new(binary, config.configurables),
salt: config.salt,
storage_slots,
})
}
pub fn regular(
code: Vec<u8>,
salt: Salt,
storage_slots: Vec<StorageSlot>,
) -> Contract<Regular> {
Contract {
code: Regular::new(code, Configurables::default()),
salt,
storage_slots,
}
}
pub async fn deploy(
self,
account: &impl Account,
tx_policies: TxPolicies,
) -> Result<DeployResponse> {
let contract_id = self.contract_id();
let state_root = self.state_root();
let salt = self.salt;
let storage_slots = self.storage_slots;
let mut tb = CreateTransactionBuilder::prepare_contract_deployment(
self.code.code(),
contract_id,
state_root,
salt,
storage_slots.to_vec(),
tx_policies,
)
.with_max_fee_estimation_tolerance(DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE);
account.add_witnesses(&mut tb)?;
account
.adjust_for_fee(&mut tb, 0)
.await
.context("failed to adjust inputs to cover for missing base asset")?;
let provider = account.try_provider()?;
let consensus_parameters = provider.consensus_parameters().await?;
let tx = tb.build(provider).await?;
let tx_id = Some(tx.id(consensus_parameters.chain_id()));
let tx_status = provider.send_transaction_and_await_commit(tx).await?;
Ok(DeployResponse {
tx_status: Some(tx_status.take_success_checked(None)?),
tx_id,
contract_id,
})
}
pub async fn deploy_if_not_exists(
self,
account: &impl Account,
tx_policies: TxPolicies,
) -> Result<DeployResponse> {
let contract_id = self.contract_id();
let provider = account.try_provider()?;
if provider.contract_exists(&contract_id).await? {
Ok(DeployResponse {
tx_status: None,
tx_id: None,
contract_id,
})
} else {
self.deploy(account, tx_policies).await
}
}
pub fn convert_to_loader(
self,
max_words_per_blob: usize,
) -> Result<Contract<Loader<BlobsNotUploaded>>> {
if max_words_per_blob == 0 {
return Err(error!(Other, "blob size must be greater than 0"));
}
let blobs = self
.code()
.chunks(max_words_per_blob.saturating_mul(WORD_SIZE))
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
Contract::loader_from_blobs(blobs, self.salt, self.storage_slots)
}
pub async fn smart_deploy(
self,
account: &impl Account,
tx_policies: TxPolicies,
max_words_per_blob: usize,
) -> Result<DeployResponse> {
let provider = account.try_provider()?;
let max_contract_size = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size() as usize;
if self.code().len() <= max_contract_size {
self.deploy(account, tx_policies).await
} else {
self.convert_to_loader(max_words_per_blob)?
.deploy(account, tx_policies)
.await
}
}
}
#[derive(Debug, Clone, Default)]
pub struct LoadConfiguration {
pub(crate) storage: StorageConfiguration,
pub(crate) configurables: Configurables,
pub(crate) salt: Salt,
}
impl LoadConfiguration {
pub fn new(
storage: StorageConfiguration,
configurables: impl Into<Configurables>,
salt: impl Into<Salt>,
) -> Self {
Self {
storage,
configurables: configurables.into(),
salt: salt.into(),
}
}
pub fn with_storage_configuration(mut self, storage: StorageConfiguration) -> Self {
self.storage = storage;
self
}
pub fn with_configurables(mut self, configurables: impl Into<Configurables>) -> Self {
self.configurables = configurables.into();
self
}
pub fn with_salt(mut self, salt: impl Into<Salt>) -> Self {
self.salt = salt.into();
self
}
}