Skip to content

Commit 159e798

Browse files
committed
add setup_contracts to contract consensus_test macros
1 parent dd1b787 commit 159e798

File tree

1 file changed

+137
-24
lines changed

1 file changed

+137
-24
lines changed

stackslib/src/chainstate/tests/consensus.rs

Lines changed: 137 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,8 @@ pub struct ContractConsensusTest<'a> {
984984
chain: ConsensusChain<'a>,
985985
/// Address of the contract deployer (the test faucet).
986986
contract_addr: StacksAddress,
987+
/// Mapping of epoch → list of prerequisite contracts to deploy.
988+
setup_contracts_per_epoch: HashMap<StacksEpochId, Vec<SetupContract>>,
987989
/// Mapping of epoch → list of `(contract_name, ClarityVersion)` deployed in that epoch.
988990
/// Multiple versions may exist per epoch (e.g., Clarity 1, 2, 3 in Epoch 3.0).
989991
contract_deploys_per_epoch: HashMap<StacksEpochId, Vec<(String, ClarityVersion)>>,
@@ -1019,6 +1021,7 @@ impl ContractConsensusTest<'_> {
10191021
/// * `contract_code` - Clarity source code of the contract
10201022
/// * `function_name` - Contract function to test
10211023
/// * `function_args` - Arguments passed to `function_name` on every call
1024+
/// * `setup_contracts` - Contracts that must be deployed before epoch-specific logic runs
10221025
///
10231026
/// # Panics
10241027
///
@@ -1034,6 +1037,7 @@ impl ContractConsensusTest<'_> {
10341037
contract_code: &str,
10351038
function_name: &str,
10361039
function_args: &[ClarityValue],
1040+
setup_contracts: &[SetupContract],
10371041
) -> Self {
10381042
assert!(
10391043
!deploy_epochs.is_empty(),
@@ -1051,15 +1055,48 @@ impl ContractConsensusTest<'_> {
10511055
HashMap::new();
10521056
let mut contract_calls_per_epoch: HashMap<StacksEpochId, Vec<String>> = HashMap::new();
10531057
let mut contract_names = vec![];
1058+
let mut setup_contracts_per_epoch: HashMap<StacksEpochId, Vec<SetupContract>> =
1059+
HashMap::new();
1060+
1061+
let mut epoch_candidates: BTreeSet<StacksEpochId> = deploy_epochs.iter().copied().collect();
1062+
epoch_candidates.extend(call_epochs.iter().copied());
1063+
let default_setup_epoch = *epoch_candidates
1064+
.iter()
1065+
.next()
1066+
.expect("deploy_epochs guarantees at least one epoch");
1067+
1068+
for contract in setup_contracts {
1069+
// Deploy the setup contracts in the first epoch if not specified.
1070+
let deploy_epoch = contract.deploy_epoch.unwrap_or(default_setup_epoch);
1071+
// Get the default Clarity version for the epoch of the contract if not specified.
1072+
let clarity_version = contract.clarity_version.or_else(|| {
1073+
if deploy_epoch < StacksEpochId::Epoch21 {
1074+
None
1075+
} else {
1076+
Some(ClarityVersion::default_for_epoch(deploy_epoch))
1077+
}
1078+
});
1079+
let mut contract = contract.clone();
1080+
contract.deploy_epoch = Some(deploy_epoch);
1081+
contract.clarity_version = clarity_version;
1082+
setup_contracts_per_epoch
1083+
.entry(deploy_epoch)
1084+
.or_default()
1085+
.push(contract);
1086+
}
10541087

10551088
// Combine and sort unique epochs
1056-
let all_epochs: BTreeSet<StacksEpochId> =
1057-
deploy_epochs.iter().chain(call_epochs).cloned().collect();
1089+
let mut all_epochs: BTreeSet<StacksEpochId> = epoch_candidates;
1090+
all_epochs.extend(setup_contracts_per_epoch.keys().copied());
10581091

10591092
// Precompute contract names and block counts
10601093
for epoch in &all_epochs {
10611094
let mut num_blocks = 0;
10621095

1096+
if let Some(contracts) = setup_contracts_per_epoch.get(epoch) {
1097+
num_blocks += contracts.len() as u64;
1098+
}
1099+
10631100
if deploy_epochs.contains(epoch) {
10641101
let clarity_versions = clarity_versions_for_epoch(*epoch);
10651102
let epoch_name = format!("Epoch{}", epoch.to_string().replace('.', "_"));
@@ -1102,6 +1139,7 @@ impl ContractConsensusTest<'_> {
11021139
contract_code: contract_code.to_string(),
11031140
function_name: function_name.to_string(),
11041141
function_args: function_args.to_vec(),
1142+
setup_contracts_per_epoch,
11051143
all_epochs,
11061144
}
11071145
}
@@ -1134,6 +1172,30 @@ impl ContractConsensusTest<'_> {
11341172
result
11351173
}
11361174

1175+
/// Deploys prerequisite contracts scheduled for the given epoch.
1176+
fn deploy_setup_contracts(&mut self, epoch: StacksEpochId) -> Vec<ExpectedResult> {
1177+
let Some(contracts) = self.setup_contracts_per_epoch.get(&epoch).cloned() else {
1178+
return vec![];
1179+
};
1180+
1181+
let is_naka_block = epoch.uses_nakamoto_blocks();
1182+
contracts
1183+
.into_iter()
1184+
.map(|contract| {
1185+
self.chain.consume_pre_naka_prepare_phase();
1186+
self.append_tx_block(
1187+
&TestTxSpec::ContractDeploy {
1188+
sender: &FAUCET_PRIV_KEY,
1189+
name: &contract.name,
1190+
code: &contract.code,
1191+
clarity_version: contract.clarity_version,
1192+
},
1193+
is_naka_block,
1194+
)
1195+
})
1196+
.collect()
1197+
}
1198+
11371199
/// Deploys all contract versions scheduled for the given epoch.
11381200
///
11391201
/// For each Clarity version supported in the epoch:
@@ -1251,6 +1313,7 @@ impl ContractConsensusTest<'_> {
12511313
.test_chainstate
12521314
.advance_into_epoch(&private_key, epoch);
12531315

1316+
results.extend(self.deploy_setup_contracts(epoch));
12541317
results.extend(self.deploy_contracts(epoch));
12551318
results.extend(self.call_contracts(epoch));
12561319
}
@@ -1464,8 +1527,9 @@ impl TestTxFactory {
14641527
/// * `contract_code` — The Clarity source code for the contract.
14651528
/// * `function_name` — The public function to call.
14661529
/// * `function_args` — Function arguments, provided as a slice of [`ClarityValue`].
1467-
/// * `deploy_epochs` — *(optional)* Epochs in which to deploy the contract. Defaults to all epochs ≥ 3.0.
1530+
/// * `deploy_epochs` — *(optional)* Epochs in which to deploy the contract. Defaults to all epochs ≥ 2.0.
14681531
/// * `call_epochs` — *(optional)* Epochs in which to call the function. Defaults to [`EPOCHS_TO_TEST`].
1532+
/// * `setup_contracts` — *(optional)* Slice of [`SetupContract`] values to deploy once before the main contract logic.
14691533
///
14701534
/// # Example
14711535
///
@@ -1474,9 +1538,15 @@ impl TestTxFactory {
14741538
/// fn test_my_contract_call_consensus() {
14751539
/// contract_call_consensus_test!(
14761540
/// contract_name: "my-contract",
1477-
/// contract_code: "(define-public (get-message) (ok \"hello\"))",
1541+
/// contract_code: "
1542+
/// (define-public (get-message)
1543+
/// (contract-call? .dependency.foo))",
14781544
/// function_name: "get-message",
14791545
/// function_args: &[],
1546+
/// setup_contracts: &[SetupContract::new(
1547+
/// "dependency",
1548+
/// "(define-public (foo) (ok \"hello\"))",
1549+
/// )],
14801550
/// );
14811551
/// }
14821552
/// ```
@@ -1488,6 +1558,7 @@ macro_rules! contract_call_consensus_test {
14881558
function_args: $function_args:expr,
14891559
$(deploy_epochs: $deploy_epochs:expr,)?
14901560
$(call_epochs: $call_epochs:expr,)?
1561+
$(setup_contracts: $setup_contracts:expr,)?
14911562
) => {
14921563
{
14931564
// Handle deploy_epochs parameter (default to all epochs >= 2.0 if not provided)
@@ -1497,6 +1568,8 @@ macro_rules! contract_call_consensus_test {
14971568
// Handle call_epochs parameter (default to EPOCHS_TO_TEST if not provided)
14981569
let call_epochs = $crate::chainstate::tests::consensus::EPOCHS_TO_TEST;
14991570
$(let call_epochs = $call_epochs;)?
1571+
let setup_contracts: &[$crate::chainstate::tests::consensus::SetupContract] = &[];
1572+
$(let setup_contracts = $setup_contracts;)?
15001573
let contract_test = $crate::chainstate::tests::consensus::ContractConsensusTest::new(
15011574
function_name!(),
15021575
vec![],
@@ -1506,6 +1579,7 @@ macro_rules! contract_call_consensus_test {
15061579
$contract_code,
15071580
$function_name,
15081581
$function_args,
1582+
setup_contracts,
15091583
);
15101584
let result = contract_test.run();
15111585
insta::assert_ron_snapshot!(result);
@@ -1532,6 +1606,7 @@ pub(crate) use contract_call_consensus_test;
15321606
/// * `contract_name` — Name of the contract being tested.
15331607
/// * `contract_code` — The Clarity source code of the contract.
15341608
/// * `deploy_epochs` — *(optional)* Epochs in which to deploy the contract. Defaults to [`EPOCHS_TO_TEST`].
1609+
/// * `setup_contracts` — *(optional)* Slice of [`SetupContract`] values to deploy before the main contract.
15351610
///
15361611
/// # Example
15371612
///
@@ -1546,34 +1621,72 @@ pub(crate) use contract_call_consensus_test;
15461621
/// }
15471622
/// ```
15481623
macro_rules! contract_deploy_consensus_test {
1549-
// Handle the case where deploy_epochs is not provided
15501624
(
15511625
contract_name: $contract_name:expr,
15521626
contract_code: $contract_code:expr,
1627+
$(deploy_epochs: $deploy_epochs:expr,)?
1628+
$(setup_contracts: $setup_contracts:expr,)?
15531629
) => {
1554-
contract_deploy_consensus_test!(
1555-
contract_name: $contract_name,
1556-
contract_code: $contract_code,
1557-
deploy_epochs: $crate::chainstate::tests::consensus::EPOCHS_TO_TEST,
1558-
);
1559-
};
1560-
(
1561-
contract_name: $contract_name:expr,
1562-
contract_code: $contract_code:expr,
1563-
deploy_epochs: $deploy_epochs:expr,
1564-
) => {
1565-
$crate::chainstate::tests::consensus::contract_call_consensus_test!(
1566-
contract_name: $contract_name,
1567-
contract_code: $contract_code,
1568-
function_name: "", // No function calls, just deploys
1569-
function_args: &[], // No function calls, just deploys
1570-
deploy_epochs: $deploy_epochs,
1571-
call_epochs: &[], // No function calls, just deploys
1572-
);
1630+
{
1631+
let deploy_epochs = $crate::chainstate::tests::consensus::EPOCHS_TO_TEST;
1632+
$(let deploy_epochs = $deploy_epochs;)?
1633+
$crate::chainstate::tests::consensus::contract_call_consensus_test!(
1634+
contract_name: $contract_name,
1635+
contract_code: $contract_code,
1636+
function_name: "", // No function calls, just deploys
1637+
function_args: &[], // No function calls, just deploys
1638+
deploy_epochs: deploy_epochs,
1639+
call_epochs: &[], // No function calls, just deploys
1640+
$(setup_contracts: $setup_contracts,)?
1641+
);
1642+
}
15731643
};
15741644
}
15751645
pub(crate) use contract_deploy_consensus_test;
15761646

1647+
/// Contract deployment that must occur before `contract_call_consensus_test!` or `contract_deploy_consensus_test!` runs its own logic.
1648+
///
1649+
/// These setups are useful when the primary contract references other contracts (traits, functions, etc.)
1650+
/// that need to exist ahead of time with deterministic names and versions.
1651+
#[derive(Clone, Debug)]
1652+
pub struct SetupContract {
1653+
/// Contract name that should be deployed (no macro suffixes applied).
1654+
pub name: String,
1655+
/// Source code for the supporting contract.
1656+
pub code: String,
1657+
/// Optional Clarity version for this contract.
1658+
pub clarity_version: Option<ClarityVersion>,
1659+
/// Optional epoch for this contract.
1660+
pub deploy_epoch: Option<StacksEpochId>,
1661+
}
1662+
1663+
impl SetupContract {
1664+
/// Creates a new SetupContract with default deployment settings.
1665+
///
1666+
/// By default, the contract will deploy in the first epoch used by the test and with the
1667+
/// default Clarity version for that epoch.
1668+
pub fn new(name: impl Into<String>, code: impl Into<String>) -> Self {
1669+
Self {
1670+
name: name.into(),
1671+
code: code.into(),
1672+
clarity_version: None,
1673+
deploy_epoch: None,
1674+
}
1675+
}
1676+
1677+
/// Override the epoch where this setup contract should deploy.
1678+
pub fn with_epoch(mut self, epoch: StacksEpochId) -> Self {
1679+
self.deploy_epoch = Some(epoch);
1680+
self
1681+
}
1682+
1683+
/// Override the Clarity version used to deploy this setup contract.
1684+
pub fn with_clarity_version(mut self, version: ClarityVersion) -> Self {
1685+
self.clarity_version = Some(version);
1686+
self
1687+
}
1688+
}
1689+
15771690
// Just a namespace for utilities for writing consensus tests
15781691
pub struct ConsensusUtils;
15791692

0 commit comments

Comments
 (0)