use starknet_accounts::{Account, AccountError, ConnectedAccount, ExecutionV1, ExecutionV3};
use starknet_core::{
types::{Call, FeeEstimate, Felt, InvokeTransactionResult, SimulatedTransaction},
utils::{get_udc_deployed_address, UdcUniqueSettings, UdcUniqueness},
};
const UDC_ADDRESS: Felt = Felt::from_raw([
121672436446604875,
9333317513348225193,
15685625669053253235,
15144800532519055890,
]);
const SELECTOR_DEPLOYCONTRACT: Felt = Felt::from_raw([
469988280392664069,
1439621915307882061,
1265649739554438882,
18249998464715511309,
]);
#[derive(Debug)]
pub struct ContractFactory<A> {
class_hash: Felt,
udc_address: Felt,
account: A,
}
#[must_use]
#[derive(Debug)]
pub struct DeploymentV1<'f, A> {
factory: &'f ContractFactory<A>,
constructor_calldata: Vec<Felt>,
salt: Felt,
unique: bool,
nonce: Option<Felt>,
max_fee: Option<Felt>,
fee_estimate_multiplier: f64,
}
#[must_use]
#[derive(Debug)]
pub struct DeploymentV3<'f, A> {
factory: &'f ContractFactory<A>,
constructor_calldata: Vec<Felt>,
salt: Felt,
unique: bool,
nonce: Option<Felt>,
gas: Option<u64>,
gas_price: Option<u128>,
gas_estimate_multiplier: f64,
gas_price_estimate_multiplier: f64,
}
impl<A> ContractFactory<A> {
pub const fn new(class_hash: Felt, account: A) -> Self {
Self::new_with_udc(class_hash, account, UDC_ADDRESS)
}
pub const fn new_with_udc(class_hash: Felt, account: A, udc_address: Felt) -> Self {
Self {
class_hash,
udc_address,
account,
}
}
}
impl<A> ContractFactory<A>
where
A: Account,
{
pub const fn deploy_v1(
&self,
constructor_calldata: Vec<Felt>,
salt: Felt,
unique: bool,
) -> DeploymentV1<'_, A> {
DeploymentV1 {
factory: self,
constructor_calldata,
salt,
unique,
nonce: None,
max_fee: None,
fee_estimate_multiplier: 1.1,
}
}
pub const fn deploy_v3(
&self,
constructor_calldata: Vec<Felt>,
salt: Felt,
unique: bool,
) -> DeploymentV3<'_, A> {
DeploymentV3 {
factory: self,
constructor_calldata,
salt,
unique,
nonce: None,
gas: None,
gas_price: None,
gas_estimate_multiplier: 1.5,
gas_price_estimate_multiplier: 1.5,
}
}
#[deprecated = "use version specific variants (`deploy_v1` & `deploy_v3`) instead"]
pub const fn deploy(
&self,
constructor_calldata: Vec<Felt>,
salt: Felt,
unique: bool,
) -> DeploymentV1<'_, A> {
self.deploy_v1(constructor_calldata, salt, unique)
}
}
impl<'f, A> DeploymentV1<'f, A> {
pub fn nonce(self, nonce: Felt) -> Self {
Self {
nonce: Some(nonce),
..self
}
}
pub fn max_fee(self, max_fee: Felt) -> Self {
Self {
max_fee: Some(max_fee),
..self
}
}
pub fn fee_estimate_multiplier(self, fee_estimate_multiplier: f64) -> Self {
Self {
fee_estimate_multiplier,
..self
}
}
}
impl<'f, A> DeploymentV3<'f, A> {
pub fn nonce(self, nonce: Felt) -> Self {
Self {
nonce: Some(nonce),
..self
}
}
pub fn gas(self, gas: u64) -> Self {
Self {
gas: Some(gas),
..self
}
}
pub fn gas_price(self, gas_price: u128) -> Self {
Self {
gas_price: Some(gas_price),
..self
}
}
pub fn gas_estimate_multiplier(self, gas_estimate_multiplier: f64) -> Self {
Self {
gas_estimate_multiplier,
..self
}
}
pub fn gas_price_estimate_multiplier(self, gas_price_estimate_multiplier: f64) -> Self {
Self {
gas_price_estimate_multiplier,
..self
}
}
}
impl<'f, A> DeploymentV1<'f, A>
where
A: Account,
{
pub fn deployed_address(&self) -> Felt {
get_udc_deployed_address(
self.salt,
self.factory.class_hash,
&if self.unique {
UdcUniqueness::Unique(UdcUniqueSettings {
deployer_address: self.factory.account.address(),
udc_contract_address: self.factory.udc_address,
})
} else {
UdcUniqueness::NotUnique
},
&self.constructor_calldata,
)
}
}
impl<'f, A> DeploymentV3<'f, A>
where
A: Account,
{
pub fn deployed_address(&self) -> Felt {
get_udc_deployed_address(
self.salt,
self.factory.class_hash,
&if self.unique {
UdcUniqueness::Unique(UdcUniqueSettings {
deployer_address: self.factory.account.address(),
udc_contract_address: self.factory.udc_address,
})
} else {
UdcUniqueness::NotUnique
},
&self.constructor_calldata,
)
}
}
impl<'f, A> DeploymentV1<'f, A>
where
A: ConnectedAccount + Sync,
{
pub async fn estimate_fee(&self) -> Result<FeeEstimate, AccountError<A::SignError>> {
let execution: ExecutionV1<'_, A> = self.into();
execution.estimate_fee().await
}
pub async fn simulate(
&self,
skip_validate: bool,
skip_fee_charge: bool,
) -> Result<SimulatedTransaction, AccountError<A::SignError>> {
let execution: ExecutionV1<'_, A> = self.into();
execution.simulate(skip_validate, skip_fee_charge).await
}
pub async fn send(&self) -> Result<InvokeTransactionResult, AccountError<A::SignError>> {
let execution: ExecutionV1<'_, A> = self.into();
execution.send().await
}
}
impl<'f, A> DeploymentV3<'f, A>
where
A: ConnectedAccount + Sync,
{
pub async fn estimate_fee(&self) -> Result<FeeEstimate, AccountError<A::SignError>> {
let execution: ExecutionV3<'_, A> = self.into();
execution.estimate_fee().await
}
pub async fn simulate(
&self,
skip_validate: bool,
skip_fee_charge: bool,
) -> Result<SimulatedTransaction, AccountError<A::SignError>> {
let execution: ExecutionV3<'_, A> = self.into();
execution.simulate(skip_validate, skip_fee_charge).await
}
pub async fn send(&self) -> Result<InvokeTransactionResult, AccountError<A::SignError>> {
let execution: ExecutionV3<'_, A> = self.into();
execution.send().await
}
}
impl<'f, A> From<&DeploymentV1<'f, A>> for ExecutionV1<'f, A> {
fn from(value: &DeploymentV1<'f, A>) -> Self {
let mut calldata = vec![
value.factory.class_hash,
value.salt,
if value.unique { Felt::ONE } else { Felt::ZERO },
value.constructor_calldata.len().into(),
];
calldata.extend_from_slice(&value.constructor_calldata);
let execution = Self::new(
vec![Call {
to: value.factory.udc_address,
selector: SELECTOR_DEPLOYCONTRACT,
calldata,
}],
&value.factory.account,
);
let execution = if let Some(nonce) = value.nonce {
execution.nonce(nonce)
} else {
execution
};
let execution = if let Some(max_fee) = value.max_fee {
execution.max_fee(max_fee)
} else {
execution
};
execution.fee_estimate_multiplier(value.fee_estimate_multiplier)
}
}
impl<'f, A> From<&DeploymentV3<'f, A>> for ExecutionV3<'f, A> {
fn from(value: &DeploymentV3<'f, A>) -> Self {
let mut calldata = vec![
value.factory.class_hash,
value.salt,
if value.unique { Felt::ONE } else { Felt::ZERO },
value.constructor_calldata.len().into(),
];
calldata.extend_from_slice(&value.constructor_calldata);
let execution = Self::new(
vec![Call {
to: value.factory.udc_address,
selector: SELECTOR_DEPLOYCONTRACT,
calldata,
}],
&value.factory.account,
);
let execution = if let Some(nonce) = value.nonce {
execution.nonce(nonce)
} else {
execution
};
let execution = if let Some(gas) = value.gas {
execution.gas(gas)
} else {
execution
};
let execution = if let Some(gas_price) = value.gas_price {
execution.gas_price(gas_price)
} else {
execution
};
let execution = execution.gas_estimate_multiplier(value.gas_estimate_multiplier);
execution.gas_price_estimate_multiplier(value.gas_price_estimate_multiplier)
}
}
#[cfg(test)]
mod tests {
use starknet_accounts::{ExecutionEncoding, SingleOwnerAccount};
use starknet_core::chain_id;
use starknet_providers::SequencerGatewayProvider;
use starknet_signers::{LocalWallet, SigningKey};
use super::*;
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_deployed_address_unique() {
let factory = ContractFactory::new(
Felt::from_hex("0x2bfd9564754d9b4a326da62b2f22b8fea7bbeffd62da4fcaea986c323b7aeb")
.unwrap(),
SingleOwnerAccount::new(
SequencerGatewayProvider::starknet_alpha_sepolia(),
LocalWallet::from_signing_key(SigningKey::from_random()),
Felt::from_hex("0xb1461de04c6a1aa3375bdf9b7723a8779c082ffe21311d683a0b15c078b5dc")
.unwrap(),
chain_id::SEPOLIA,
ExecutionEncoding::Legacy,
),
);
let unique_address_v1 = factory
.deploy_v1(
vec![Felt::from_hex("0x1234").unwrap()],
Felt::from_hex("0x3456").unwrap(),
true,
)
.deployed_address();
let unique_address_v3 = factory
.deploy_v3(
vec![Felt::from_hex("0x1234").unwrap()],
Felt::from_hex("0x3456").unwrap(),
true,
)
.deployed_address();
let not_unique_address_v1 = factory
.deploy_v1(
vec![Felt::from_hex("0x1234").unwrap()],
Felt::from_hex("0x3456").unwrap(),
false,
)
.deployed_address();
let not_unique_address_v3 = factory
.deploy_v3(
vec![Felt::from_hex("0x1234").unwrap()],
Felt::from_hex("0x3456").unwrap(),
false,
)
.deployed_address();
assert_eq!(
unique_address_v1,
Felt::from_hex("0x36e05bcd41191387bc2f04ed9cad4776a75df3b748b0246a5d217a988474181")
.unwrap()
);
assert_eq!(
unique_address_v3,
Felt::from_hex("0x36e05bcd41191387bc2f04ed9cad4776a75df3b748b0246a5d217a988474181")
.unwrap()
);
assert_eq!(
not_unique_address_v1,
Felt::from_hex("0x3a320b6aa0b451b22fba90b5d75b943932649137c09a86a5cf4853031be70c1")
.unwrap()
);
assert_eq!(
not_unique_address_v3,
Felt::from_hex("0x3a320b6aa0b451b22fba90b5d75b943932649137c09a86a5cf4853031be70c1")
.unwrap()
);
}
}