Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 123 additions & 26 deletions contracts/contract-factory/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![no_std]
use soroban_sdk::{
contract, contractimpl, contracttype, symbol_short, vec, xdr::ToXdr, Address, Bytes, BytesN,
Env, Symbol, Val, Vec,
contract, contracterror, contractimpl, contracttype, symbol_short, vec, xdr::ToXdr, Address,
Bytes, BytesN, Env, Symbol, Val, Vec,
};

const DEPLOYED_CONTRACT: Symbol = symbol_short!("DEPLOYED");
Expand All @@ -13,12 +13,35 @@ const INSTANCE_EXTEND_TO: u32 = 30 * DAY_IN_LEDGERS;
#[contract]
pub struct ContractFactory;

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum FactoryError {
DeploymentFailed = 1,
InnerCallFailed = 2,
}

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContractDeploymentArgs {
wasm_hash: BytesN<32>,
salt: BytesN<32>,
constructor_args: Vec<Val>,
pub wasm_hash: BytesN<32>,
pub salt: BytesN<32>,
pub constructor_args: Vec<Val>,
}

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContractCall {
pub target: Address,
pub function: Symbol,
pub args: Vec<Val>,
}

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DeployAndCallResult {
pub address: Address,
pub results: Vec<Val>,
}

#[contracttype]
Expand Down Expand Up @@ -76,8 +99,31 @@ impl ContractFactory {
contract_id
}

fn predict_and_check_deployed(
env: &Env,
deployment_args: &ContractDeploymentArgs,
) -> (Address, bool) {
let tentative_contract_id = Self::get_deployed_address(
env,
deployment_args.salt.clone(),
deployment_args.wasm_hash.clone(),
deployment_args.constructor_args.clone(),
);
let is_deployed = env
.try_invoke_contract::<bool, soroban_sdk::Error>(
&tentative_contract_id,
&Symbol::new(env, "is_deployed"),
Vec::new(env),
)
.is_ok();
(tentative_contract_id, is_deployed)
}

/// Deploys a contract on behalf of the `ContractFactory` contract.
pub fn deploy(env: &Env, deployment_args: ContractDeploymentArgs) -> Address {
pub fn deploy(
env: &Env,
deployment_args: ContractDeploymentArgs,
) -> Result<Address, FactoryError> {
Self::extend_instance_ttl(env);
let ContractDeploymentArgs {
wasm_hash,
Expand All @@ -86,39 +132,85 @@ impl ContractFactory {
} = deployment_args;

let derived_salt = Self::derive_salt(env, salt, &wasm_hash, &constructor_args);
Self::deploy_and_emit(env, derived_salt, wasm_hash, constructor_args)
Ok(Self::deploy_and_emit(
env,
Comment on lines 134 to +136

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return DeploymentFailed instead of always wrapping deploy in Ok

The new Result<..., FactoryError> surface never actually emits FactoryError::DeploymentFailed: deploy (and similarly deploy_idempotent/upload_and_deploy) unconditionally wraps deploy_and_emit in Ok(...). When deployment fails (e.g., duplicate deployment path already covered by tests), deploy_v2 traps before a Result is returned, so callers cannot pattern-match the typed deployment error this API now advertises.

Useful? React with 👍 / 👎.

derived_salt,
wasm_hash,
constructor_args,
))
}

/// Deploys a contract on behalf of the `ContractFactory` contract.
/// If the contract is already deployed at the deterministic address, returns it.
pub fn deploy_idempotent(env: &Env, deployment_args: ContractDeploymentArgs) -> Address {
pub fn deploy_idempotent(
env: &Env,
deployment_args: ContractDeploymentArgs,
) -> Result<Address, FactoryError> {
Self::extend_instance_ttl(env);
let (tentative_contract_id, is_deployed) =
Self::predict_and_check_deployed(env, &deployment_args);

if is_deployed {
return Ok(tentative_contract_id);
}

let ContractDeploymentArgs {
wasm_hash,
salt,
constructor_args,
} = deployment_args;

let tentative_contract_id = Self::get_deployed_address(
let derived_salt = Self::derive_salt(env, salt, &wasm_hash, &constructor_args);
Ok(Self::deploy_and_emit(
env,
salt.clone(),
wasm_hash.clone(),
constructor_args.clone(),
);
let is_deployed = env
.try_invoke_contract::<bool, soroban_sdk::Error>(
&tentative_contract_id,
&Symbol::new(env, "is_deployed"),
Vec::new(env),
)
.is_ok();
derived_salt,
wasm_hash,
constructor_args,
))
}

if is_deployed {
return tentative_contract_id;
/// Idempotently deploys a contract and then dispatches a sequence of inner
/// contract calls, returning the deployed address alongside the raw return
/// value of each inner call.
///
/// If any inner call reverts, `FactoryError::InnerCallFailed` is returned
/// and the whole transaction is rolled back by the host.
pub fn deploy_idempotent_and_call(
env: &Env,
deployment_args: ContractDeploymentArgs,
calls: Vec<ContractCall>,
) -> Result<DeployAndCallResult, FactoryError> {
Self::extend_instance_ttl(env);

let (tentative_contract_id, is_deployed) =
Self::predict_and_check_deployed(env, &deployment_args);

let address = if is_deployed {
tentative_contract_id
} else {
let ContractDeploymentArgs {
wasm_hash,
salt,
constructor_args,
} = deployment_args;
let derived_salt = Self::derive_salt(env, salt, &wasm_hash, &constructor_args);
Self::deploy_and_emit(env, derived_salt, wasm_hash, constructor_args)
};

let mut results: Vec<Val> = Vec::new(env);
for call in calls.iter() {
let result = env
.try_invoke_contract::<Val, soroban_sdk::Error>(
&call.target,
Comment thread
alberto-crossmint marked this conversation as resolved.
&call.function,
call.args.clone(),
)
.map_err(|_| FactoryError::InnerCallFailed)?
.map_err(|_| FactoryError::InnerCallFailed)?;
results.push_back(result);
}

let derived_salt = Self::derive_salt(env, salt, &wasm_hash, &constructor_args);
Self::deploy_and_emit(env, derived_salt, wasm_hash, constructor_args)
Ok(DeployAndCallResult { address, results })
}

/// Uploads the contract WASM and deploys it on behalf of the `ContractFactory` contract.
Expand All @@ -127,11 +219,16 @@ impl ContractFactory {
wasm_bytes: Bytes,
salt: BytesN<32>,
constructor_args: Vec<Val>,
) -> Address {
) -> Result<Address, FactoryError> {
Self::extend_instance_ttl(env);
let wasm_hash = env.deployer().upload_contract_wasm(wasm_bytes);
let derived_salt = Self::derive_salt(env, salt, &wasm_hash, &constructor_args);
Self::deploy_and_emit(env, derived_salt, wasm_hash, constructor_args)
Ok(Self::deploy_and_emit(
env,
derived_salt,
wasm_hash,
constructor_args,
))
}

pub fn get_deployed_address(
Expand Down
Loading
Loading