use super::NotPreparedError;
use async_trait::async_trait;
use starknet_core::{
crypto::compute_hash_on_elements,
types::{
BlockId, BlockTag, BroadcastedDeployAccountTransaction, BroadcastedTransaction,
DeployAccountTransactionResult, FeeEstimate, FieldElement, SimulatedTransaction,
SimulationFlag, StarknetError,
},
};
use starknet_providers::{Provider, ProviderError};
use std::error::Error;
pub mod argent;
pub mod open_zeppelin;
const PREFIX_DEPLOY_ACCOUNT: FieldElement = FieldElement::from_mont([
3350261884043292318,
18443211694809419988,
18446744073709551615,
461298303000467581,
]);
const PREFIX_CONTRACT_ADDRESS: FieldElement = FieldElement::from_mont([
3829237882463328880,
17289941567720117366,
8635008616843941496,
533439743893157637,
]);
const ADDR_BOUND: FieldElement = FieldElement::from_mont([
18446743986131443745,
160989183,
18446744073709255680,
576459263475590224,
]);
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait AccountFactory: Sized {
type Provider: Provider + Sync;
type SignError: Error + Send + Sync;
fn class_hash(&self) -> FieldElement;
fn calldata(&self) -> Vec<FieldElement>;
fn chain_id(&self) -> FieldElement;
fn provider(&self) -> &Self::Provider;
fn block_id(&self) -> BlockId {
BlockId::Tag(BlockTag::Latest)
}
async fn sign_deployment(
&self,
deployment: &RawAccountDeployment,
) -> Result<Vec<FieldElement>, Self::SignError>;
fn deploy(&self, salt: FieldElement) -> AccountDeployment<Self> {
AccountDeployment::new(salt, self)
}
}
#[must_use]
#[derive(Debug)]
pub struct AccountDeployment<'f, F> {
factory: &'f F,
salt: FieldElement,
nonce: Option<FieldElement>,
max_fee: Option<FieldElement>,
fee_estimate_multiplier: f64,
}
#[derive(Debug, Clone)]
pub struct RawAccountDeployment {
salt: FieldElement,
nonce: FieldElement,
max_fee: FieldElement,
}
#[derive(Debug)]
pub struct PreparedAccountDeployment<'f, F> {
factory: &'f F,
inner: RawAccountDeployment,
}
#[derive(Debug, thiserror::Error)]
pub enum AccountFactoryError<S> {
#[error(transparent)]
Signing(S),
#[error(transparent)]
Provider(ProviderError),
}
impl<'f, F> AccountDeployment<'f, F> {
pub fn new(salt: FieldElement, factory: &'f F) -> Self {
Self {
factory,
salt,
nonce: None,
max_fee: None,
fee_estimate_multiplier: 1.1,
}
}
pub fn nonce(self, nonce: FieldElement) -> Self {
Self {
nonce: Some(nonce),
..self
}
}
pub fn max_fee(self, max_fee: FieldElement) -> Self {
Self {
max_fee: Some(max_fee),
..self
}
}
pub fn fee_estimate_multiplier(self, fee_estimate_multiplier: f64) -> Self {
Self {
fee_estimate_multiplier,
..self
}
}
pub fn prepared(self) -> Result<PreparedAccountDeployment<'f, F>, NotPreparedError> {
let nonce = self.nonce.ok_or(NotPreparedError)?;
let max_fee = self.max_fee.ok_or(NotPreparedError)?;
Ok(PreparedAccountDeployment {
factory: self.factory,
inner: RawAccountDeployment {
salt: self.salt,
nonce,
max_fee,
},
})
}
}
impl<'f, F> AccountDeployment<'f, F>
where
F: AccountFactory + Sync,
{
pub fn address(&self) -> FieldElement {
calculate_contract_address(
self.salt,
self.factory.class_hash(),
&self.factory.calldata(),
)
}
pub async fn fetch_nonce(&self) -> Result<FieldElement, ProviderError> {
match self
.factory
.provider()
.get_nonce(self.factory.block_id(), self.address())
.await
{
Ok(nonce) => Ok(nonce),
Err(ProviderError::StarknetError(StarknetError::ContractNotFound)) => {
Ok(FieldElement::ZERO)
}
Err(err) => Err(err),
}
}
pub async fn estimate_fee(&self) -> Result<FeeEstimate, AccountFactoryError<F::SignError>> {
let nonce = match self.nonce {
Some(value) => value,
None => self
.fetch_nonce()
.await
.map_err(AccountFactoryError::Provider)?,
};
self.estimate_fee_with_nonce(nonce).await
}
pub async fn simulate(
&self,
skip_validate: bool,
skip_fee_charge: bool,
) -> Result<SimulatedTransaction, AccountFactoryError<F::SignError>> {
let nonce = match self.nonce {
Some(value) => value,
None => self
.fetch_nonce()
.await
.map_err(AccountFactoryError::Provider)?,
};
self.simulate_with_nonce(nonce, skip_validate, skip_fee_charge)
.await
}
pub async fn send(
&self,
) -> Result<DeployAccountTransactionResult, AccountFactoryError<F::SignError>> {
self.prepare().await?.send().await
}
async fn prepare(
&self,
) -> Result<PreparedAccountDeployment<'f, F>, AccountFactoryError<F::SignError>> {
let nonce = match self.nonce {
Some(value) => value,
None => self
.fetch_nonce()
.await
.map_err(AccountFactoryError::Provider)?,
};
let max_fee = match self.max_fee {
Some(value) => value,
None => {
let fee_estimate = self.estimate_fee_with_nonce(nonce).await?;
((fee_estimate.overall_fee as f64 * self.fee_estimate_multiplier) as u64).into()
}
};
Ok(PreparedAccountDeployment {
factory: self.factory,
inner: RawAccountDeployment {
salt: self.salt,
nonce,
max_fee,
},
})
}
async fn estimate_fee_with_nonce(
&self,
nonce: FieldElement,
) -> Result<FeeEstimate, AccountFactoryError<F::SignError>> {
let prepared = PreparedAccountDeployment {
factory: self.factory,
inner: RawAccountDeployment {
salt: self.salt,
nonce,
max_fee: FieldElement::ZERO,
},
};
let deploy = prepared
.get_deploy_request()
.await
.map_err(AccountFactoryError::Signing)?;
self.factory
.provider()
.estimate_fee_single(
BroadcastedTransaction::DeployAccount(deploy),
self.factory.block_id(),
)
.await
.map_err(AccountFactoryError::Provider)
}
async fn simulate_with_nonce(
&self,
nonce: FieldElement,
skip_validate: bool,
skip_fee_charge: bool,
) -> Result<SimulatedTransaction, AccountFactoryError<F::SignError>> {
let prepared = PreparedAccountDeployment {
factory: self.factory,
inner: RawAccountDeployment {
salt: self.salt,
nonce,
max_fee: self.max_fee.unwrap_or_default(),
},
};
let deploy = prepared
.get_deploy_request()
.await
.map_err(AccountFactoryError::Signing)?;
let mut flags = vec![];
if skip_validate {
flags.push(SimulationFlag::SkipValidate);
}
if skip_fee_charge {
flags.push(SimulationFlag::SkipFeeCharge);
}
self.factory
.provider()
.simulate_transaction(
self.factory.block_id(),
BroadcastedTransaction::DeployAccount(deploy),
&flags,
)
.await
.map_err(AccountFactoryError::Provider)
}
}
impl<'f, F> PreparedAccountDeployment<'f, F> {
pub fn from_raw(raw_deployment: RawAccountDeployment, factory: &'f F) -> Self {
Self {
factory,
inner: raw_deployment,
}
}
}
impl<'f, F> PreparedAccountDeployment<'f, F>
where
F: AccountFactory,
{
pub fn address(&self) -> FieldElement {
calculate_contract_address(
self.inner.salt,
self.factory.class_hash(),
&self.factory.calldata(),
)
}
pub fn transaction_hash(&self) -> FieldElement {
let mut calldata_to_hash = vec![self.factory.class_hash(), self.inner.salt];
calldata_to_hash.append(&mut self.factory.calldata());
compute_hash_on_elements(&[
PREFIX_DEPLOY_ACCOUNT,
FieldElement::ONE, self.address(),
FieldElement::ZERO, compute_hash_on_elements(&calldata_to_hash),
self.inner.max_fee,
self.factory.chain_id(),
self.inner.nonce,
])
}
pub async fn send(
&self,
) -> Result<DeployAccountTransactionResult, AccountFactoryError<F::SignError>> {
let tx_request = self
.get_deploy_request()
.await
.map_err(AccountFactoryError::Signing)?;
self.factory
.provider()
.add_deploy_account_transaction(tx_request)
.await
.map_err(AccountFactoryError::Provider)
}
async fn get_deploy_request(
&self,
) -> Result<BroadcastedDeployAccountTransaction, F::SignError> {
let signature = self.factory.sign_deployment(&self.inner).await?;
Ok(BroadcastedDeployAccountTransaction {
max_fee: self.inner.max_fee,
signature,
nonce: self.inner.nonce,
contract_address_salt: self.inner.salt,
constructor_calldata: self.factory.calldata(),
class_hash: self.factory.class_hash(),
is_query: false,
})
}
}
fn calculate_contract_address(
salt: FieldElement,
class_hash: FieldElement,
constructor_calldata: &[FieldElement],
) -> FieldElement {
compute_hash_on_elements(&[
PREFIX_CONTRACT_ADDRESS,
FieldElement::ZERO,
salt,
class_hash,
compute_hash_on_elements(constructor_calldata),
]) % ADDR_BOUND
}