Skip to content

Commit 30ec996

Browse files
committed
chore: initial tests
1 parent c91e868 commit 30ec996

File tree

10 files changed

+1589
-97
lines changed

10 files changed

+1589
-97
lines changed

crates/algokit_test_artifacts/contracts/state_contract/state.arc56.json

Lines changed: 714 additions & 0 deletions
Large diffs are not rendered by default.

crates/algokit_test_artifacts/contracts/testing_app_arc56/app_spec.arc56.json

Lines changed: 681 additions & 0 deletions
Large diffs are not rendered by default.

crates/algokit_test_artifacts/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,13 @@ pub mod testing_app_puya {
178178
include_str!("../contracts/testing_app_puya/application.arc56.json");
179179
}
180180

181+
/// Testing app ARC56 templates (control-template capable)
182+
pub mod testing_app_arc56_templates {
183+
/// ARC56 app spec used in template-var/error mapping tests
184+
pub const APP_SPEC_ARC56: &str =
185+
include_str!("../contracts/testing_app_arc56/app_spec.arc56.json");
186+
}
187+
181188
/// Extra pages test contract artifacts
182189
pub mod extra_pages_test {
183190
/// Aggregate application (ARC56) used by extra pages tests
@@ -191,6 +198,12 @@ pub mod extra_pages_test {
191198
pub const LARGE_ARC56: &str = include_str!("../contracts/extra_pages_test/large.arc56.json");
192199
}
193200

201+
/// State contract artifacts (control-aware spec)
202+
pub mod state_contract {
203+
/// State contract (ARC56) with UPDATABLE/DELETABLE/VALUE template variables
204+
pub const STATE_ARC56: &str = include_str!("../contracts/state_contract/state.arc56.json");
205+
}
206+
194207
/// Resource population contract artifacts
195208
pub mod resource_population {
196209
/// Resource population testing contract (ARC32) targeting AVM V8

crates/algokit_utils/src/applications/app_client/mod.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use algokit_abi::Arc56Contract;
22
use std::collections::HashMap;
33

44
use crate::AlgorandClient;
5-
use crate::applications::AppDeployer;
65
use crate::clients::network_client::NetworkDetails;
76
use algokit_transact::Address;
87
use std::str::FromStr;
@@ -119,12 +118,7 @@ impl AppClient {
119118
let address = Address::from_str(creator_address)
120119
.map_err(|e| AppClientError::Lookup(format!("Invalid creator address: {}", e)))?;
121120

122-
let indexer_client = algorand.client().indexer();
123-
let mut app_deployer = AppDeployer::new(
124-
algorand.app().clone(),
125-
algorand.send().clone(),
126-
Some(indexer_client),
127-
);
121+
let mut app_deployer = algorand.app_deployer();
128122

129123
let lookup = app_deployer
130124
.get_creator_apps_by_name(&address, ignore_cache)

crates/algokit_utils/src/applications/app_deployer.rs

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,8 @@ impl AppDeployer {
503503

504504
// Check for changes
505505
let is_update = self.is_program_different(&approval_bytes, &clear_bytes, &existing_app)?;
506-
let is_schema_break = self.is_schema_break(&create_params, &existing_app)?;
506+
let is_schema_break =
507+
self.is_schema_break(&create_params, &existing_app, &approval_bytes, &clear_bytes)?;
507508

508509
if is_schema_break {
509510
self.handle_schema_break(
@@ -565,11 +566,39 @@ impl AppDeployer {
565566
),
566567
})?;
567568

568-
// Query indexer for apps created by this address
569-
let created_apps_response = indexer
570-
.lookup_account_created_applications(&creator_address_str, None, Some(true), None, None)
571-
.await
572-
.map_err(|e| AppDeployError::IndexerError { source: e })?;
569+
// Query indexer for apps created by this address; localnet-only retry to allow catch-up
570+
let is_localnet = self.app_manager.is_localnet().await.unwrap_or(false);
571+
let mut created_apps_response_opt = None;
572+
let mut tries: u32 = 0;
573+
let max_tries: u32 = if is_localnet { 100 } else { 1 };
574+
while tries < max_tries {
575+
match indexer
576+
.lookup_account_created_applications(
577+
&creator_address_str,
578+
None,
579+
Some(true),
580+
None,
581+
None,
582+
)
583+
.await
584+
{
585+
Ok(resp) => {
586+
created_apps_response_opt = Some(resp);
587+
break;
588+
}
589+
Err(e) => {
590+
if !is_localnet {
591+
return Err(AppDeployError::IndexerError { source: e });
592+
}
593+
tries += 1;
594+
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
595+
}
596+
}
597+
}
598+
let created_apps_response =
599+
created_apps_response_opt.ok_or_else(|| AppDeployError::DeploymentLookupFailed {
600+
message: String::from("Indexer did not catch up in time on localnet"),
601+
})?;
573602

574603
let mut app_lookup = HashMap::new();
575604

@@ -714,17 +743,23 @@ impl AppDeployer {
714743
) -> Result<(Vec<u8>, Vec<u8>), AppDeployError> {
715744
let approval_bytes = match approval_program {
716745
AppProgram::Teal(code) => {
717-
let deployment_metadata_for_compilation = DeploymentMetadata {
746+
// Always pass through provided deploy-time controls; AppManager enforces token presence
747+
let metadata = DeploymentMetadata {
718748
updatable: deployment_metadata.updatable,
719749
deletable: deployment_metadata.deletable,
720750
};
751+
info!(
752+
"Compiling approval TEAL with controls: updatable={:?}, deletable={:?}",
753+
metadata.updatable, metadata.deletable
754+
);
755+
let metadata_opt = if metadata.updatable.is_some() || metadata.deletable.is_some() {
756+
Some(&metadata)
757+
} else {
758+
None
759+
};
721760
let compiled = self
722761
.app_manager
723-
.compile_teal_template(
724-
code,
725-
deploy_time_params,
726-
Some(&deployment_metadata_for_compilation),
727-
)
762+
.compile_teal_template(code, deploy_time_params, metadata_opt)
728763
.await
729764
.map_err(|e| AppDeployError::AppManagerError { source: e })?;
730765
compiled.compiled_base64_to_bytes
@@ -774,20 +809,24 @@ impl AppDeployer {
774809
&self,
775810
create_params: &CreateParams,
776811
existing_app: &AppInformation,
812+
approval_program: &[u8],
813+
clear_state_program: &[u8],
777814
) -> Result<bool, AppDeployError> {
778-
let (new_global_schema, new_local_schema, new_extra_pages) = match create_params {
815+
let (new_global_schema, new_local_schema) = match create_params {
779816
CreateParams::AppCreateCall(params) => (
780817
params.global_state_schema.as_ref(),
781818
params.local_state_schema.as_ref(),
782-
params.extra_program_pages.unwrap_or(0),
783819
),
784820
CreateParams::AppCreateMethodCall(params) => (
785821
params.global_state_schema.as_ref(),
786822
params.local_state_schema.as_ref(),
787-
params.extra_program_pages.unwrap_or(0),
788823
),
789824
};
790825

826+
// Compute extra program pages from the compiled program bytes to match Python behavior
827+
let new_extra_pages =
828+
Self::calculate_extra_program_pages(approval_program, clear_state_program);
829+
791830
let global_ints_break =
792831
new_global_schema.is_some_and(|schema| schema.num_uints > existing_app.global_ints);
793832
let global_bytes_break = new_global_schema
@@ -961,9 +1000,7 @@ impl AppDeployer {
9611000
clear_state_program: clear_state_program.to_vec(),
9621001
global_state_schema: params.global_state_schema.clone(),
9631002
local_state_schema: params.local_state_schema.clone(),
964-
extra_program_pages: params
965-
.extra_program_pages
966-
.or_else(|| Some(computed_extra_pages)),
1003+
extra_program_pages: params.extra_program_pages.or(Some(computed_extra_pages)),
9671004
args: params.args.clone(),
9681005
account_references: params.account_references.clone(),
9691006
app_references: params.app_references.clone(),
@@ -985,9 +1022,7 @@ impl AppDeployer {
9851022
clear_state_program: clear_state_program.to_vec(),
9861023
global_state_schema: params.global_state_schema.clone(),
9871024
local_state_schema: params.local_state_schema.clone(),
988-
extra_program_pages: params
989-
.extra_program_pages
990-
.or_else(|| Some(computed_extra_pages)),
1025+
extra_program_pages: params.extra_program_pages.or(Some(computed_extra_pages)),
9911026
method: params.method.clone(),
9921027
args: params.args.clone(),
9931028
account_references: params.account_references.clone(),
@@ -1179,9 +1214,7 @@ impl AppDeployer {
11791214
clear_state_program: clear_state_program.to_vec(),
11801215
global_state_schema: params.global_state_schema.clone(),
11811216
local_state_schema: params.local_state_schema.clone(),
1182-
extra_program_pages: params
1183-
.extra_program_pages
1184-
.or_else(|| Some(computed_extra_pages)),
1217+
extra_program_pages: params.extra_program_pages.or(Some(computed_extra_pages)),
11851218
args: params.args.clone(),
11861219
account_references: params.account_references.clone(),
11871220
app_references: params.app_references.clone(),
@@ -1202,9 +1235,7 @@ impl AppDeployer {
12021235
clear_state_program: clear_state_program.to_vec(),
12031236
global_state_schema: params.global_state_schema.clone(),
12041237
local_state_schema: params.local_state_schema.clone(),
1205-
extra_program_pages: params
1206-
.extra_program_pages
1207-
.or_else(|| Some(computed_extra_pages)),
1238+
extra_program_pages: params.extra_program_pages.or(Some(computed_extra_pages)),
12081239
method: params.method.clone(),
12091240
args: params.args.clone(),
12101241
account_references: params.account_references.clone(),

crates/algokit_utils/src/applications/app_factory/compilation.rs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,22 @@ impl AppFactory {
3737
.get_decoded_clear()
3838
.map_err(|e| AppFactoryError::CompilationError(e.to_string()))?;
3939

40+
let metadata = crate::clients::app_manager::DeploymentMetadata {
41+
updatable: self.updatable,
42+
deletable: self.deletable,
43+
};
44+
let metadata_opt = if metadata.updatable.is_some() || metadata.deletable.is_some() {
45+
Some(&metadata)
46+
} else {
47+
None
48+
};
4049
let approval = self
4150
.algorand()
4251
.app()
4352
.compile_teal_template(
4453
&approval_teal,
4554
self.deploy_time_params.as_ref(),
46-
Some(&crate::clients::app_manager::DeploymentMetadata {
47-
updatable: self.updatable,
48-
deletable: self.deletable,
49-
}),
55+
metadata_opt,
5056
)
5157
.await
5258
.map_err(|e| AppFactoryError::CompilationError(e.to_string()))?;
@@ -88,17 +94,19 @@ impl AppFactory {
8894
.get_decoded_clear()
8995
.map_err(|e| AppFactoryError::CompilationError(e.to_string()))?;
9096

97+
let metadata = crate::clients::app_manager::DeploymentMetadata {
98+
updatable: cp.updatable,
99+
deletable: cp.deletable,
100+
};
101+
let metadata_opt = if metadata.updatable.is_some() || metadata.deletable.is_some() {
102+
Some(&metadata)
103+
} else {
104+
None
105+
};
91106
let approval = self
92107
.algorand()
93108
.app()
94-
.compile_teal_template(
95-
&approval_teal,
96-
cp.deploy_time_params.as_ref(),
97-
Some(&crate::clients::app_manager::DeploymentMetadata {
98-
updatable: cp.updatable,
99-
deletable: cp.deletable,
100-
}),
101-
)
109+
.compile_teal_template(&approval_teal, cp.deploy_time_params.as_ref(), metadata_opt)
102110
.await
103111
.map_err(|e| AppFactoryError::CompilationError(e.to_string()))?;
104112

crates/algokit_utils/src/applications/app_factory/mod.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,25 @@ impl AppFactory {
190190
AppFactoryError,
191191
> {
192192
// Prepare create/update/delete deploy params
193-
let resolved_updatable = self.updatable;
194-
let resolved_deletable = self.deletable;
193+
// Auto-detect deploy-time controls if not explicitly provided
194+
let mut resolved_updatable = self.updatable;
195+
let mut resolved_deletable = self.deletable;
196+
if resolved_updatable.is_none() || resolved_deletable.is_none() {
197+
if let Some(source) = self.app_spec().source.as_ref() {
198+
if let Ok(approval_teal) = source.get_decoded_approval() {
199+
let has_updatable = approval_teal
200+
.contains(crate::clients::app_manager::UPDATABLE_TEMPLATE_NAME);
201+
let has_deletable = approval_teal
202+
.contains(crate::clients::app_manager::DELETABLE_TEMPLATE_NAME);
203+
if resolved_updatable.is_none() && has_updatable {
204+
resolved_updatable = Some(true);
205+
}
206+
if resolved_deletable.is_none() && has_deletable {
207+
resolved_deletable = Some(true);
208+
}
209+
}
210+
}
211+
}
195212
let resolved_deploy_time_params = self.deploy_time_params.clone();
196213

197214
let create_deploy_params = match create_params {
@@ -241,11 +258,7 @@ impl AppFactory {
241258
send_params: send_params.unwrap_or_default(),
242259
};
243260

244-
let mut app_deployer = crate::applications::AppDeployer::new(
245-
self.algorand.app().clone(),
246-
self.algorand.send().clone(),
247-
Some(self.algorand.client().indexer()),
248-
);
261+
let mut app_deployer = self.algorand.app_deployer();
249262

250263
let deploy_result = app_deployer
251264
.deploy(deploy_params)

crates/algokit_utils/src/clients/algorand_client.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::applications::AppDeployer;
12
use crate::clients::app_manager::AppManager;
23
use crate::clients::asset_manager::AssetManager;
34
use crate::clients::client_manager::ClientManager;
@@ -15,6 +16,7 @@ pub struct AlgorandClient {
1516
transaction_sender: TransactionSender,
1617
transaction_creator: TransactionCreator,
1718
account_manager: Arc<Mutex<AccountManager>>,
19+
app_deployer: AppDeployer,
1820
}
1921

2022
impl AlgorandClient {
@@ -56,13 +58,21 @@ impl AlgorandClient {
5658
// Create closure for TransactionCreator
5759
let transaction_creator = TransactionCreator::new(new_group.clone());
5860

61+
// Persistent AppDeployer with shared in-memory cache and indexer
62+
let app_deployer = AppDeployer::new(
63+
app_manager.clone(),
64+
transaction_sender.clone(),
65+
Some(client_manager.indexer()),
66+
);
67+
5968
Self {
6069
client_manager,
6170
account_manager: account_manager.clone(),
6271
asset_manager,
6372
app_manager,
6473
transaction_sender,
6574
transaction_creator,
75+
app_deployer,
6676
}
6777
}
6878

@@ -145,4 +155,9 @@ impl AlgorandClient {
145155
.unwrap()
146156
.set_signer(sender, signer);
147157
}
158+
159+
/// Get a clone of the persistent AppDeployer (shares cache across clones)
160+
pub fn app_deployer(&self) -> AppDeployer {
161+
self.app_deployer.clone()
162+
}
148163
}

crates/algokit_utils/src/clients/app_manager.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::clients::network_client::genesis_id_is_localnet;
12
use algod_client::{
23
apis::{AlgodClient, Error as AlgodError},
34
models::TealKeyValue,
@@ -367,6 +368,16 @@ impl AppManager {
367368
Ok(values)
368369
}
369370

371+
/// Determine if the connected network is a localnet by inspecting genesis ID
372+
pub async fn is_localnet(&self) -> Result<bool, AppManagerError> {
373+
let params = self
374+
.algod_client
375+
.transaction_params()
376+
.await
377+
.map_err(|e| AppManagerError::AlgodClientError { source: e })?;
378+
Ok(genesis_id_is_localnet(&params.genesis_id))
379+
}
380+
370381
/// Get ABI return value from transaction confirmation.
371382
pub fn get_abi_return(
372383
confirmation_data: &[u8],

0 commit comments

Comments
 (0)