From 1d0137ea2a5375d731f02f2fbfc37ae92e1861cf Mon Sep 17 00:00:00 2001
From: Cyle Witruk <236413682+cylewitruk-stacks@users.noreply.github.com>
Date: Mon, 22 Jun 2026 12:43:19 +0200
Subject: [PATCH 1/5] cost voting epoch-4 gating, minor cleanups and tests
---
clarity/src/vm/costs/mod.rs | 29 +-
clarity/src/vm/tests/epoch_gating.rs | 4 +-
stacks-common/src/types/mod.rs | 94 ++-
stacks-common/src/types/tests.rs | 107 ++++
stackslib/src/chainstate/tests/consensus.rs | 14 +-
.../chainstate/tests/consensus_unit_tests.rs | 6 +-
.../tests/runtime_analysis_tests.rs | 4 +-
.../src/chainstate/tests/runtime_tests.rs | 6 +-
stackslib/src/clarity_vm/clarity.rs | 165 ++++--
stackslib/src/clarity_vm/tests/costs.rs | 545 +++++++++++++-----
stackslib/src/net/api/tests/postblock_v3.rs | 1 -
11 files changed, 730 insertions(+), 245 deletions(-)
diff --git a/clarity/src/vm/costs/mod.rs b/clarity/src/vm/costs/mod.rs
index 9efcab817cd..9e14915e88c 100644
--- a/clarity/src/vm/costs/mod.rs
+++ b/clarity/src/vm/costs/mod.rs
@@ -953,18 +953,29 @@ impl TrackerData {
))
})?;
+ // TODO(cost-voting): Remove after epoch 4.0 activation (when cost-voting deactivates), and
+ // use `CostStateSummary::empty()` directly instead.
+ let state_summary = if epoch_id.supports_cost_voting_contract() {
+ // If cost-voting is active, load and apply the current cost function configuration from
+ // the chain tip, applying any changes that have been voted on but not yet applied.
+ load_cost_functions(self.mainnet, clarity_db, apply_updates).map_err(|e| {
+ let result = clarity_db
+ .roll_back()
+ .map_err(|e| CostErrors::Expect(e.to_string()));
+ match result {
+ Ok(_) => e,
+ Err(rollback_err) => rollback_err,
+ }
+ })?
+ } else {
+ // Cost-voting is retired at Epoch 4.0: every cost function uses boot defaults.
+ CostStateSummary::empty()
+ };
+
let CostStateSummary {
contract_call_circuits,
mut cost_function_references,
- } = load_cost_functions(self.mainnet, clarity_db, apply_updates).map_err(|e| {
- let result = clarity_db
- .roll_back()
- .map_err(|e| CostErrors::Expect(e.to_string()));
- match result {
- Ok(_) => e,
- Err(rollback_err) => rollback_err,
- }
- })?;
+ } = state_summary;
self.contract_call_circuits = contract_call_circuits;
diff --git a/clarity/src/vm/tests/epoch_gating.rs b/clarity/src/vm/tests/epoch_gating.rs
index 902bdbb26a8..2faf3ae86db 100644
--- a/clarity/src/vm/tests/epoch_gating.rs
+++ b/clarity/src/vm/tests/epoch_gating.rs
@@ -12,7 +12,7 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-use stacks_common::types::StacksEpochId;
+use stacks_common::types::{StacksEpochId, StacksEpochRangeTestExt as _};
use crate::vm::ClarityVersion;
@@ -21,7 +21,7 @@ use crate::vm::ClarityVersion;
#[test]
fn test_default_for_epoch_is_monotonic() {
// No Clarity in Epoch10.
- let clarity_epochs = &StacksEpochId::since(StacksEpochId::Epoch20);
+ let clarity_epochs = (StacksEpochId::Epoch20..).as_slice();
for window in clarity_epochs.windows(2) {
let earlier = ClarityVersion::default_for_epoch(window[0]);
let later = ClarityVersion::default_for_epoch(window[1]);
diff --git a/stacks-common/src/types/mod.rs b/stacks-common/src/types/mod.rs
index 9ed0a014c5d..f86afc73e3f 100644
--- a/stacks-common/src/types/mod.rs
+++ b/stacks-common/src/types/mod.rs
@@ -17,6 +17,8 @@
use std::cmp::Ordering;
use std::fmt;
use std::io::{Read, Write};
+#[cfg(any(test, feature = "testing"))]
+use std::ops::{Bound, RangeBounds};
use std::ops::{Deref, DerefMut, Index, IndexMut};
use std::str::FromStr;
use std::sync::LazyLock;
@@ -596,6 +598,12 @@ impl StacksEpochId {
}
}
+ /// Whether or not this epoch supports the cost-voting contract (SIP-006), which is
+ /// disabled from Epoch 4.0 (SIP-044).
+ pub fn supports_cost_voting_contract(&self) -> bool {
+ self < &StacksEpochId::Epoch40
+ }
+
/// Returns true for epochs which use Nakamoto blocks. These blocks use a
/// different header format than the previous Stacks blocks, which among
/// other changes includes a Stacks-specific timestamp.
@@ -814,34 +822,84 @@ impl StacksEpochId {
StacksEpochId::Epoch40 => PEER_VERSION_EPOCH_4_0,
}
}
+}
- #[cfg(any(test, feature = "testing"))]
- pub fn since(epoch: StacksEpochId) -> &'static [StacksEpochId] {
- let idx = Self::ALL
+/// Test-only helper functions for `StacksEpochId`.
+///
+/// These functions rely on the [`StacksEpochId::ALL`] array of all defined epochs and are only
+/// intended to be used in testing as they may return variants that are not yet active according to
+/// [`StacksEpochId::RELEASE_LATEST_EPOCH`].
+#[cfg(any(test, feature = "testing"))]
+impl StacksEpochId {
+ /// Gets the index of the provided `epoch` within the [`ALL`](StacksEpochId::ALL) array of
+ /// defined epochs.
+ fn index_of(epoch: Self) -> usize {
+ Self::ALL
.iter()
.position(|&e| e == epoch)
- .expect("epoch not found in ALL");
+ .expect("epoch not found in ALL")
+ }
- &Self::ALL[idx..]
+ /// Returns all [`StacksEpochId`] variants after the provided `epoch` (exclusive).
+ ///
+ /// Provided as a helper function since this can't be expressed as a range (there's no
+ /// "excluded" start-bound syntax).
+ ///
+ /// Useful for iterating over all epochs _after_ a specific epoch, when the next epoch may not
+ /// yet be known or defined (e.g. in tests that want to assert an invariant for all future
+ /// [`StacksEpochId`] variants).
+ pub fn all_after(epoch: Self) -> impl Iterator- {
+ (Bound::Excluded(epoch), Bound::Unbounded).iter().copied()
}
- /// Returns all [`StacksEpochId`] from `start` to `end`, both inclusive.
- #[cfg(any(test, feature = "testing"))]
- pub fn between(start: StacksEpochId, end: StacksEpochId) -> &'static [StacksEpochId] {
- let start_idx = Self::ALL
- .iter()
- .position(|&e| e == start)
- .expect("start epoch not found in ALL");
- let end_idx = Self::ALL
- .iter()
- .position(|&e| e == end)
- .expect("end epoch not found in ALL");
- assert!(start_idx <= end_idx, "start epoch must be <= end epoch");
+ /// Returns the first (lowest) [`StacksEpochId`] variant.
+ pub const fn first() -> StacksEpochId {
+ Self::ALL[0]
+ }
+
+ /// Returns the last (highest) defined [`StacksEpochId`] variant.
+ pub const fn last() -> StacksEpochId {
+ Self::ALL[Self::ALL.len() - 1]
+ }
+}
+
+/// Extension methods for iterating over standard Rust range bounds of [`StacksEpochId`].
+/// Note: When `Step` stabilizes, this can be refactored.
+#[cfg(any(test, feature = "testing"))]
+pub trait StacksEpochRangeTestExt: RangeBounds + Sized {
+ /// Iterates by reference over all [`StacksEpochId`] variants in this range.
+ ///
+ /// Forgiving: behaves like standard `Range` iterators in that `start >= end` results in an
+ /// empty iterator.
+ fn iter(&self) -> std::slice::Iter<'static, StacksEpochId> {
+ let start = match self.start_bound() {
+ Bound::Included(epoch) => StacksEpochId::index_of(*epoch),
+ Bound::Excluded(epoch) => StacksEpochId::index_of(*epoch) + 1,
+ Bound::Unbounded => 0,
+ };
+
+ let end = match self.end_bound() {
+ Bound::Included(epoch) => StacksEpochId::index_of(*epoch) + 1,
+ Bound::Excluded(epoch) => StacksEpochId::index_of(*epoch),
+ Bound::Unbounded => StacksEpochId::ALL.len(),
+ };
- &Self::ALL[start_idx..=end_idx]
+ // Yield an empty slice if end <= start, mirroring standard Rust behavior.
+ let end = end.max(start);
+ StacksEpochId::ALL[start..end].iter()
+ }
+
+ /// Returns a slice of all [`StacksEpochId`] variants in this range.
+ fn as_slice(&self) -> &'static [StacksEpochId] {
+ self.iter().as_slice()
}
}
+/// Implement [`StacksEpochRangeTestExt`] for [`StacksEpochId`] ranges (e.g.
+/// `StacksEpochId::Epoch10..=StacksEpochId::Epoch23`).
+#[cfg(any(test, feature = "testing"))]
+impl StacksEpochRangeTestExt for R where R: RangeBounds {}
+
impl PartialOrd for StacksAddress {
fn partial_cmp(&self, other: &StacksAddress) -> Option {
Some(self.cmp(other))
diff --git a/stacks-common/src/types/tests.rs b/stacks-common/src/types/tests.rs
index f5b8274f577..60de824bcd1 100644
--- a/stacks-common/src/types/tests.rs
+++ b/stacks-common/src/types/tests.rs
@@ -14,12 +14,119 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+use std::ops::Bound;
use std::str::FromStr;
use super::{
set_test_coinbase_schedule, CoinbaseInterval, StacksEpochId, COINBASE_INTERVALS_MAINNET,
COINBASE_INTERVALS_TESTNET,
};
+use crate::types::StacksEpochRangeTestExt;
+
+#[test]
+fn test_epoch40_cost_voting_contract_support_gate() {
+ // Up to (excluding) Epoch 4.0 = enabled.
+ for epoch in (..StacksEpochId::Epoch40).iter() {
+ assert!(
+ epoch.supports_cost_voting_contract(),
+ "cost-voting gate must remain active before Epoch 4.0 for {epoch}"
+ );
+ }
+
+ // Epoch 4.0 and onward = disabled.
+ for epoch in (StacksEpochId::Epoch40..).iter() {
+ assert!(
+ !epoch.supports_cost_voting_contract(),
+ "cost-voting gate must be inactive from Epoch 4.0 onward for {epoch}"
+ );
+ }
+}
+
+#[test]
+fn test_epoch_range_ext_iter() {
+ // Alias to keep the below cleaner
+ fn epoch_index(epoch: StacksEpochId) -> usize {
+ StacksEpochId::index_of(epoch)
+ }
+
+ // Full range is effectively equalent to the ALL constant.
+ assert_eq!((..).as_slice(), StacksEpochId::ALL);
+
+ // Start = inclusive, end = unbounded
+ assert_eq!(
+ (StacksEpochId::Epoch32..).as_slice(),
+ &StacksEpochId::ALL[epoch_index(StacksEpochId::Epoch32)..]
+ );
+
+ // Start = unbounded, end = exclusive
+ assert_eq!(
+ (..StacksEpochId::Epoch21).as_slice(),
+ &[
+ StacksEpochId::Epoch10,
+ StacksEpochId::Epoch20,
+ StacksEpochId::Epoch2_05
+ ]
+ );
+
+ // Start = unbounded, end = inclusive
+ assert_eq!(
+ (..=StacksEpochId::Epoch21).as_slice(),
+ &[
+ StacksEpochId::Epoch10,
+ StacksEpochId::Epoch20,
+ StacksEpochId::Epoch2_05,
+ StacksEpochId::Epoch21
+ ]
+ );
+
+ // Start = inclusive, end = exclusive
+ assert_eq!(
+ (StacksEpochId::Epoch20..StacksEpochId::Epoch22).as_slice(),
+ &[
+ StacksEpochId::Epoch20,
+ StacksEpochId::Epoch2_05,
+ StacksEpochId::Epoch21
+ ]
+ );
+
+ // Start = inclusive, end = inclusive
+ assert_eq!(
+ (StacksEpochId::Epoch20..=StacksEpochId::Epoch22).as_slice(),
+ &[
+ StacksEpochId::Epoch20,
+ StacksEpochId::Epoch2_05,
+ StacksEpochId::Epoch21,
+ StacksEpochId::Epoch22
+ ]
+ );
+
+ // Start = exclusive, end = unbounded (a'la `all_after`).
+ let expected = &StacksEpochId::ALL[epoch_index(StacksEpochId::Epoch32) + 1..];
+ assert_eq!(
+ (Bound::Excluded(StacksEpochId::Epoch32), Bound::Unbounded).as_slice(),
+ expected
+ );
+ let all_after: Vec<_> = StacksEpochId::all_after(StacksEpochId::Epoch32).collect();
+ assert_eq!(&all_after, expected);
+
+ // Single element range with exclusive end bound should yield empty.
+ assert_eq!(
+ (StacksEpochId::Epoch23..StacksEpochId::Epoch23).as_slice(),
+ &[]
+ );
+
+ // Single element range with inclusive end bound should yield that item.
+ assert_eq!(
+ (StacksEpochId::Epoch23..=StacksEpochId::Epoch23).as_slice(),
+ &[StacksEpochId::Epoch23]
+ );
+
+ // Reversed range should yield empty.
+ assert_eq!(
+ (StacksEpochId::Epoch22..StacksEpochId::Epoch20).as_slice(),
+ &[]
+ );
+}
#[test]
fn test_stacks_epoch_id_display_fromstr_tryfrom_roundtrip() {
diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs
index 582171d1aa0..45cb8e5c812 100644
--- a/stackslib/src/chainstate/tests/consensus.rs
+++ b/stackslib/src/chainstate/tests/consensus.rs
@@ -19,7 +19,7 @@ use clarity::boot_util::boot_code_addr;
use clarity::codec::StacksMessageCodec;
use clarity::consts::{CHAIN_ID_TESTNET, STACKS_EPOCH_MAX};
use clarity::types::chainstate::{StacksAddress, StacksPrivateKey, StacksPublicKey, TrieHash};
-use clarity::types::{EpochList, StacksEpoch, StacksEpochId};
+use clarity::types::{EpochList, StacksEpoch, StacksEpochId, StacksEpochRangeTestExt as _};
use clarity::util::hash::{Hash160, MerkleTree, Sha512Trunc256Sum};
use clarity::util::secp256k1::MessageSignature;
use clarity::vm::costs::ExecutionCost;
@@ -72,14 +72,14 @@ pub fn max_tested_epoch() -> StacksEpochId {
.expect("EPOCHS_TO_TEST must contain at least one epoch")
}
-/// Like [`StacksEpochId::since`], but clamped to [`max_tested_epoch`]. Tests use
-/// this to build deploy/call epoch ranges so that excluded epochs are never
-/// deployed in or called in. Deploying or calling in an excluded epoch would
-/// otherwise keep that epoch's results (marf hashes, costs) in the snapshots and
-/// reintroduce the churn that excluding it is meant to avoid.
+/// Like a `(start..)` range, but clamped to [`max_tested_epoch`].
+///
+/// Tests use this to build deploy/call epoch ranges so that excluded epochs are never used, which
+/// otherwise would keep that epoch's results (marf hashes, costs) in the snapshots and reintroduce
+/// the churn that excluding it is meant to avoid.
pub fn tested_epochs_since(start: StacksEpochId) -> Vec {
let max = max_tested_epoch();
- StacksEpochId::since(start)
+ (start..)
.iter()
.copied()
.filter(|epoch| *epoch <= max)
diff --git a/stackslib/src/chainstate/tests/consensus_unit_tests.rs b/stackslib/src/chainstate/tests/consensus_unit_tests.rs
index 4c9932e1337..218736f7551 100644
--- a/stackslib/src/chainstate/tests/consensus_unit_tests.rs
+++ b/stackslib/src/chainstate/tests/consensus_unit_tests.rs
@@ -13,7 +13,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-use clarity::types::StacksEpochId;
+use clarity::types::{StacksEpochId, StacksEpochRangeTestExt as _};
use clarity::vm::{ClarityVersion, Value};
use crate::chainstate::tests::consensus::{
@@ -25,7 +25,7 @@ fn test_example_1_cdeploy() {
let report = contract_deploy_consensus_unit_test!(
contract_name: "map_empty",
contract_code: "(map + (list) (list 10 20))",
- deploy_epochs: StacksEpochId::since(StacksEpochId::Epoch20),
+ deploy_epochs: (StacksEpochId::Epoch20..).as_slice(),
clarity_versions: ClarityVersion::ALL,
);
@@ -57,7 +57,7 @@ fn test_example_2_ccall() {
",
function_name: "trigger",
function_args: &[],
- deploy_epochs: StacksEpochId::since(StacksEpochId::Epoch20),
+ deploy_epochs: (StacksEpochId::Epoch20..).as_slice(),
clarity_versions: ClarityVersion::ALL,
);
diff --git a/stackslib/src/chainstate/tests/runtime_analysis_tests.rs b/stackslib/src/chainstate/tests/runtime_analysis_tests.rs
index d6e66595a10..85eb9b6accf 100644
--- a/stackslib/src/chainstate/tests/runtime_analysis_tests.rs
+++ b/stackslib/src/chainstate/tests/runtime_analysis_tests.rs
@@ -17,7 +17,7 @@
use std::collections::HashMap;
-use clarity::types::StacksEpochId;
+use clarity::types::{StacksEpochId, StacksEpochRangeTestExt as _};
#[allow(unused_imports)]
use clarity::vm::analysis::RuntimeCheckErrorKind;
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, MAX_TYPE_DEPTH};
@@ -613,7 +613,7 @@ fn runtime_check_error_kind_type_error_ccall() {
(get-shares u999 .pool))",
function_name: "trigger-error",
function_args: &[],
- deploy_epochs: StacksEpochId::between(StacksEpochId::Epoch20, StacksEpochId::Epoch33),
+ deploy_epochs: (StacksEpochId::Epoch20..=StacksEpochId::Epoch33).as_slice(),
call_epochs: &[StacksEpochId::Epoch33],
setup_contracts: &[contract_1, contract_2],
);
diff --git a/stackslib/src/chainstate/tests/runtime_tests.rs b/stackslib/src/chainstate/tests/runtime_tests.rs
index 7c4ef09aa86..6b77b00494a 100644
--- a/stackslib/src/chainstate/tests/runtime_tests.rs
+++ b/stackslib/src/chainstate/tests/runtime_tests.rs
@@ -18,7 +18,7 @@
use std::collections::HashMap;
use clarity::types::chainstate::{StacksPrivateKey, StacksPublicKey};
-use clarity::types::StacksEpochId;
+use clarity::types::{StacksEpochId, StacksEpochRangeTestExt as _};
use clarity::vm::errors::RuntimeError;
use clarity::vm::types::{PrincipalData, ResponseData};
use clarity::vm::{max_call_stack_depth_for_epoch, ClarityVersion, Value as ClarityValue};
@@ -682,7 +682,7 @@ fn unknown_block_header_hash_fork() {
)",
function_name: "trigger",
function_args: &[],
- deploy_epochs: StacksEpochId::between(StacksEpochId::Epoch20, StacksEpochId::Epoch33),
+ deploy_epochs: (StacksEpochId::Epoch20..=StacksEpochId::Epoch33).as_slice(),
call_epochs: &[StacksEpochId::Epoch33],
);
}
@@ -708,7 +708,7 @@ fn bad_block_hash() {
)",
function_name: "trigger",
function_args: &[],
- deploy_epochs: StacksEpochId::between(StacksEpochId::Epoch20, StacksEpochId::Epoch33),
+ deploy_epochs: (StacksEpochId::Epoch20..=StacksEpochId::Epoch33).as_slice(),
call_epochs: &[StacksEpochId::Epoch33],
);
}
diff --git a/stackslib/src/clarity_vm/clarity.rs b/stackslib/src/clarity_vm/clarity.rs
index 5d3e44961b2..50f3451f3b0 100644
--- a/stackslib/src/clarity_vm/clarity.rs
+++ b/stackslib/src/clarity_vm/clarity.rs
@@ -1037,6 +1037,77 @@ impl<'a, 'b> ClarityBlockConnection<'a, 'b> {
Ok(boot_code_account)
}
+ /// Prepares a [`StacksTransaction`] with a [`SmartContract`](TransactionPayload::SmartContract)
+ /// payload for instantiating a boot contract, but does not execute it.
+ ///
+ /// Sets the transaction's auth to be the boot code account and the transaction version based on
+ /// this instance's `mainnet` field.
+ fn make_boot_code_smart_contract_tx(
+ &mut self,
+ contract_name: &str,
+ code_body: &str,
+ clarity_version: Option,
+ ) -> Result<(StacksAccount, StacksTransaction), ClarityError> {
+ let boot_code_account = self.get_boot_code_account()?;
+ let tx_version = if self.mainnet {
+ TransactionVersion::Mainnet
+ } else {
+ TransactionVersion::Testnet
+ };
+
+ let boot_code_auth = boot_code_tx_auth(boot_code_addr(self.mainnet));
+ let payload = TransactionPayload::SmartContract(
+ TransactionSmartContract {
+ name: ContractName::try_from(contract_name.to_string())
+ .expect("FATAL: invalid boot-code contract name"),
+ code_body: StacksString::from_str(code_body)
+ .expect("FATAL: invalid boot code body"),
+ },
+ clarity_version,
+ );
+
+ Ok((
+ boot_code_account,
+ StacksTransaction::new(tx_version, boot_code_auth, payload),
+ ))
+ }
+
+ /// Instantiates a boot contract by:
+ ///
+ /// 1. Preparing a [`StacksTransaction`] with the appropriate version, payload and auth,
+ /// 2. Executing it using the boot account within a cost-free transaction, and
+ /// 3. Asserting the receipt for success.
+ ///
+ /// Panics if any of the above steps fail.
+ fn instantiate_boot_contract(
+ &mut self,
+ contract_name: &str,
+ code_body: &str,
+ clarity_version: Option,
+ ) -> Result {
+ let contract_id = boot_code_id(contract_name, self.mainnet);
+
+ let (boot_code_account, contract_tx) =
+ self.make_boot_code_smart_contract_tx(contract_name, code_body, clarity_version)?;
+
+ let receipt = self.as_free_transaction(|tx_conn| {
+ info!("Instantiate {} contract", &contract_id);
+ StacksChainState::process_transaction_payload(
+ tx_conn,
+ &contract_tx,
+ &boot_code_account,
+ None,
+ )
+ .expect("FATAL: Failed to process boot contract initialization")
+ });
+
+ if receipt.result != Value::okay_true() || receipt.post_condition_aborted {
+ panic!("FATAL: Failure processing {contract_id} contract initialization: {receipt:#?}");
+ }
+
+ Ok(receipt)
+ }
+
pub fn initialize_epoch_2_05(&mut self) -> Result {
// use the `using!` statement to ensure that the old cost_tracker is placed
// back in all branches after initialization
@@ -2002,6 +2073,27 @@ impl<'a, 'b> ClarityBlockConnection<'a, 'b> {
})
}
+ /// Instantiates epoch 4.0's `costs-5` cost contract using the [`COSTS_5_NAME`] and
+ /// [`BOOT_CODE_COSTS_5`] constants.
+ ///
+ /// Returns an error if this instance's epoch is not already set to
+ /// [`Epoch40`](StacksEpochId::Epoch40).
+ ///
+ /// Used both by epoch 4.0 initialization and by tests which need to instantiate the costs-5
+ /// contract without full epoch 4.0 initialization.
+ pub(super) fn instantiate_epoch_4_0_cost_contract(
+ &mut self,
+ ) -> Result {
+ if self.epoch != StacksEpochId::Epoch40 {
+ return Err(ClarityError::BadTransaction(format!(
+ "Epoch 4.0 cost contract initialization requires Epoch 4.0 rules; current epoch is {}",
+ self.epoch
+ )));
+ }
+
+ self.instantiate_boot_contract(COSTS_5_NAME, BOOT_CODE_COSTS_5, None)
+ }
+
pub fn initialize_epoch_4_0(&mut self) -> Result, ClarityError> {
// use the `using!` statement to ensure that the old cost_tracker is placed
// back in all branches after initialization
@@ -2036,70 +2128,21 @@ impl<'a, 'b> ClarityBlockConnection<'a, 'b> {
.expect("PANIC: PoX-5 first reward cycle begins *before* first burn block height")
+ 1;
- let boot_code_account = self
- .get_boot_code_account()
- .expect("FATAL: did not get boot account");
-
- let mainnet = self.mainnet;
- let tx_version = if mainnet {
- TransactionVersion::Mainnet
- } else {
- TransactionVersion::Testnet
- };
-
- let boot_code_address = boot_code_addr(mainnet);
- let boot_code_auth = boot_code_tx_auth(boot_code_address);
-
/////////////////// .costs-5 ////////////////////////
- let costs_5_contract_id = boot_code_id(COSTS_5_NAME, mainnet);
- let payload = TransactionPayload::SmartContract(
- TransactionSmartContract {
- name: ContractName::try_from(COSTS_5_NAME)
- .expect("FATAL: invalid boot-code contract name"),
- code_body: StacksString::from_str(BOOT_CODE_COSTS_5)
- .expect("FATAL: invalid boot code body"),
- },
- None,
- );
-
- let costs_5_contract_tx =
- StacksTransaction::new(tx_version, boot_code_auth.clone(), payload);
-
- let costs_5_initialization_receipt = self.as_transaction(|tx_conn| {
- debug!("Instantiate {} contract", &costs_5_contract_id);
- let receipt = StacksChainState::process_transaction_payload(
- tx_conn,
- &costs_5_contract_tx,
- &boot_code_account,
- None,
- )
- .expect("FATAL: Failed to process .costs-5 contract initialization");
- receipt
- });
-
- if costs_5_initialization_receipt.result != Value::okay_true()
- || costs_5_initialization_receipt.post_condition_aborted
- {
- panic!(
- "FATAL: Failure processing .costs-5 contract initialization: {:#?}",
- &costs_5_initialization_receipt
- );
- }
+ let costs_5_initialization_receipt = self
+ .instantiate_epoch_4_0_cost_contract()
+ .expect("FATAL: Failed to initialize Epoch 4.0 cost contract");
/////////////////// .pox-5 ////////////////////////
- let pox_5_contract_id = boot_code_id(POX_5_NAME, mainnet);
- let pox_5_code = make_pox_5_body(mainnet);
- let payload = TransactionPayload::SmartContract(
- TransactionSmartContract {
- name: ContractName::try_from(POX_5_NAME)
- .expect("FATAL: invalid boot-code contract name"),
- code_body: StacksString::from_str(&pox_5_code)
- .expect("FATAL: invalid boot code body"),
- },
- Some(ClarityVersion::Clarity6),
- );
-
- let pox_5_contract_tx = StacksTransaction::new(tx_version, boot_code_auth, payload);
+ let pox_5_contract_id = boot_code_id(POX_5_NAME, self.mainnet);
+ let pox_5_code = make_pox_5_body(self.mainnet);
+ let (boot_code_account, pox_5_contract_tx) = self
+ .make_boot_code_smart_contract_tx(
+ POX_5_NAME,
+ &pox_5_code,
+ Some(ClarityVersion::Clarity6),
+ )
+ .expect("FATAL: Failed to construct .pox-5 contract initialization");
let pox_5_initialization_receipt = self.as_transaction(|tx_conn| {
debug!("Instantiate {} contract", &pox_5_contract_id);
diff --git a/stackslib/src/clarity_vm/tests/costs.rs b/stackslib/src/clarity_vm/tests/costs.rs
index 7796007ac3f..7e2b782c0fc 100644
--- a/stackslib/src/clarity_vm/tests/costs.rs
+++ b/stackslib/src/clarity_vm/tests/costs.rs
@@ -22,6 +22,7 @@ use clarity::vm::costs::{
DefaultVersion, ExecutionCost, LimitedCostTracker, COSTS_1_NAME, COSTS_2_NAME, COSTS_3_NAME,
COSTS_4_NAME,
};
+use clarity::vm::database::clarity_db::ClarityDatabase;
use clarity::vm::errors::VmExecutionError;
use clarity::vm::events::StacksTransactionEvent;
use clarity::vm::functions::NativeFunctions;
@@ -215,10 +216,21 @@ fn execute_transaction(
env.execute_transaction(issuer, None, contract_identifier.clone(), tx, args)
}
-fn with_owned_env(epoch: StacksEpochId, use_mainnet: bool, to_do: F) -> R
-where
- F: Fn(OwnedEnvironment) -> R,
-{
+// Epochs which deploy boot contracts needed for costs tests.
+const COST_TEST_SETUP_EPOCHS: [StacksEpochId; 4] = [
+ StacksEpochId::Epoch2_05,
+ StacksEpochId::Epoch21,
+ StacksEpochId::Epoch33,
+ StacksEpochId::Epoch40,
+];
+
+fn next_test_block_id(block_id_byte: &mut u8) -> StacksBlockId {
+ let block_id = StacksBlockId([*block_id_byte; 32]);
+ *block_id_byte += 1;
+ block_id
+}
+
+fn new_cost_test_clarity_instance(use_mainnet: bool) -> (ClarityInstance, StacksBlockId, u8) {
let marf_kv = MarfedKV::temporary();
let chain_id = test_only_mainnet_to_chain_id(use_mainnet);
let mut clarity_instance = ClarityInstance::new(use_mainnet, chain_id, marf_kv);
@@ -233,52 +245,74 @@ where
)
.commit_block();
- let mut tip = first_block.clone();
+ (clarity_instance, first_block, 1)
+}
- if epoch >= StacksEpochId::Epoch2_05 {
- let burn_state_db = generate_test_burn_state_db(StacksEpochId::Epoch2_05);
- let next_block = StacksBlockId([1; 32]);
- let mut clarity_conn =
- clarity_instance.begin_block(&tip, &next_block, &TEST_HEADER_DB, &burn_state_db);
- clarity_conn.initialize_epoch_2_05().unwrap();
- clarity_conn.commit_block();
- tip = next_block.clone();
+fn setup_cost_test_epoch(
+ clarity_instance: &mut ClarityInstance,
+ tip: &mut StacksBlockId,
+ block_id_byte: &mut u8,
+ setup_epoch: StacksEpochId,
+) {
+ let burn_state_db = generate_test_burn_state_db(setup_epoch);
+ let next_block = next_test_block_id(block_id_byte);
+ let mut clarity_conn =
+ clarity_instance.begin_block(tip, &next_block, &TEST_HEADER_DB, &burn_state_db);
+
+ match setup_epoch {
+ StacksEpochId::Epoch2_05 => {
+ clarity_conn.initialize_epoch_2_05().unwrap();
+ }
+ StacksEpochId::Epoch21 => {
+ clarity_conn.initialize_epoch_2_1().unwrap();
+ }
+ StacksEpochId::Epoch33 => {
+ clarity_conn.initialize_epoch_3_3().unwrap();
+ }
+ StacksEpochId::Epoch40 => {
+ // `initialize_epoch_4_0()` also deploys PoX-5, which requires the chainstate
+ // test harness's sBTC stubs. Cost tests only need the costs-5 contract.
+ clarity_conn.set_epoch_for_testing(StacksEpochId::Epoch40);
+ clarity_conn.instantiate_epoch_4_0_cost_contract().unwrap();
+ }
+ _ => unreachable!(
+ "COST_TEST_SETUP_EPOCHS only contains epochs which instantiate boot contracts needed for costs tests"
+ ),
}
- if epoch >= StacksEpochId::Epoch21 {
- let burn_state_db = generate_test_burn_state_db(StacksEpochId::Epoch21);
- let next_block = StacksBlockId([2; 32]);
- let mut clarity_conn =
- clarity_instance.begin_block(&tip, &next_block, &TEST_HEADER_DB, &burn_state_db);
- clarity_conn.initialize_epoch_2_1().unwrap();
- clarity_conn.commit_block();
- tip = next_block.clone();
- }
+ clarity_conn.commit_block();
+ *tip = next_block;
+}
- if epoch >= StacksEpochId::Epoch30 {
- let burn_state_db = generate_test_burn_state_db(StacksEpochId::Epoch30);
- let next_block = StacksBlockId([3; 32]);
- let mut clarity_conn =
- clarity_instance.begin_block(&tip, &next_block, &TEST_HEADER_DB, &burn_state_db);
- clarity_conn.initialize_epoch_3_0().unwrap();
- clarity_conn.commit_block();
- tip = next_block.clone();
- }
+fn setup_cost_test_epochs_through(
+ clarity_instance: &mut ClarityInstance,
+ tip: &mut StacksBlockId,
+ block_id_byte: &mut u8,
+ epoch: StacksEpochId,
+) {
+ for setup_epoch in COST_TEST_SETUP_EPOCHS {
+ if epoch < setup_epoch {
+ break;
+ }
- if epoch >= StacksEpochId::Epoch33 {
- let burn_state_db = generate_test_burn_state_db(StacksEpochId::Epoch33);
- let next_block = StacksBlockId([4; 32]);
- let mut clarity_conn =
- clarity_instance.begin_block(&tip, &next_block, &TEST_HEADER_DB, &burn_state_db);
- clarity_conn.initialize_epoch_3_3().unwrap();
- clarity_conn.commit_block();
- tip = next_block.clone();
+ setup_cost_test_epoch(clarity_instance, tip, block_id_byte, setup_epoch);
}
+}
+
+fn with_owned_env(epoch: StacksEpochId, use_mainnet: bool, to_do: F) -> R
+where
+ F: Fn(OwnedEnvironment) -> R,
+{
+ let (mut clarity_instance, mut tip, mut block_id_byte) =
+ new_cost_test_clarity_instance(use_mainnet);
+
+ setup_cost_test_epochs_through(&mut clarity_instance, &mut tip, &mut block_id_byte, epoch);
let mut marf_kv = clarity_instance.destroy();
let burn_state_db = generate_test_burn_state_db(epoch);
- let mut store = marf_kv.begin(&tip, &StacksBlockId([5; 32]));
+ let final_block = next_test_block_id(&mut block_id_byte);
+ let mut store = marf_kv.begin(&tip, &final_block);
to_do(OwnedEnvironment::new_max_limit(
store.as_clarity_db(&TEST_HEADER_DB, &burn_state_db),
@@ -287,6 +321,228 @@ where
))
}
+fn cost_voting_contract(use_mainnet: bool) -> &'static QualifiedContractIdentifier {
+ if use_mainnet {
+ &COST_VOTING_MAINNET_CONTRACT
+ } else {
+ &COST_VOTING_TESTNET_CONTRACT
+ }
+}
+
+struct CostProposal {
+ function_contract: PrincipalData,
+ function_name: &'static str,
+ cost_function_contract: PrincipalData,
+ cost_function_name: &'static str,
+}
+
+fn cost_proposal(
+ function_contract: impl Into,
+ function_name: &'static str,
+ cost_function_contract: impl Into,
+ cost_function_name: &'static str,
+) -> CostProposal {
+ CostProposal {
+ function_contract: function_contract.into(),
+ function_name,
+ cost_function_contract: cost_function_contract.into(),
+ cost_function_name,
+ }
+}
+
+fn write_confirmed_cost_proposals(
+ db: &mut ClarityDatabase,
+ use_mainnet: bool,
+ proposal_start_id: usize,
+ confirmed_proposal_count: usize,
+ proposals: impl IntoIterator
- ,
+) {
+ let voting_contract_to_use = cost_voting_contract(use_mainnet);
+ db.set_variable_unknown_descriptor(
+ voting_contract_to_use,
+ "confirmed-proposal-count",
+ Value::UInt(confirmed_proposal_count as u128),
+ )
+ .unwrap();
+
+ let epoch = db.get_clarity_epoch_version().unwrap();
+ for (ix, proposal) in proposals.into_iter().enumerate() {
+ let value = format!(
+ "{{ function-contract: '{},
+ function-name: \"{}\",
+ cost-function-contract: '{},
+ cost-function-name: \"{}\",
+ confirmed-height: u1 }}",
+ proposal.function_contract,
+ proposal.function_name,
+ proposal.cost_function_contract,
+ proposal.cost_function_name
+ );
+ db.set_entry_unknown_descriptor(
+ voting_contract_to_use,
+ "confirmed-proposals",
+ execute_on_network(
+ &format!("{{ confirmed-id: u{} }}", ix + proposal_start_id),
+ use_mainnet,
+ ),
+ execute_on_network(&value, use_mainnet),
+ &epoch,
+ )
+ .unwrap();
+ }
+}
+
+fn cost_voting_test_cost_definer(use_mainnet: bool) -> QualifiedContractIdentifier {
+ let p1 = execute_on_network("'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR", use_mainnet);
+ let Value::Principal(PrincipalData::Standard(p1_principal)) = p1 else {
+ panic!("Expected a standard principal data");
+ };
+ QualifiedContractIdentifier::new(p1_principal, ContractName::from_literal("cost-definer"))
+}
+
+fn deploy_cost_voting_test_cost_definer(
+ clarity_instance: &mut ClarityInstance,
+ tip: &mut StacksBlockId,
+ block_id_byte: &mut u8,
+ use_mainnet: bool,
+) -> QualifiedContractIdentifier {
+ let cost_definer = cost_voting_test_cost_definer(use_mainnet);
+ let cost_definer_src = "
+ (define-read-only (cost-definition-le (size uint))
+ {
+ runtime: u0, write_length: u0, write_count: u0, read_count: u0, read_length: u0
+ })
+ ";
+ let burn_state_db = generate_test_burn_state_db(StacksEpochId::Epoch20);
+ let next_block = next_test_block_id(block_id_byte);
+ let mut block_conn =
+ clarity_instance.begin_block(tip, &next_block, &TEST_HEADER_DB, &burn_state_db);
+ block_conn.as_transaction(|tx| {
+ let (ast, analysis) = tx
+ .analyze_smart_contract(&cost_definer, ClarityVersion::Clarity1, cost_definer_src)
+ .unwrap();
+ tx.initialize_smart_contract(
+ &cost_definer,
+ ClarityVersion::Clarity1,
+ &ast,
+ cost_definer_src,
+ None,
+ |_, _| None,
+ None,
+ )
+ .unwrap();
+ tx.save_analysis(&cost_definer, &analysis).unwrap();
+ });
+ block_conn.commit_block();
+ *tip = next_block;
+
+ cost_definer
+}
+
+fn write_voted_le_cost_state(
+ clarity_instance: &mut ClarityInstance,
+ tip: &mut StacksBlockId,
+ block_id_byte: &mut u8,
+ vote_state_epoch: StacksEpochId,
+ use_mainnet: bool,
+ cost_definer: &QualifiedContractIdentifier,
+) {
+ let burn_state_db = generate_test_burn_state_db(vote_state_epoch);
+ let next_block = next_test_block_id(block_id_byte);
+
+ let mut clarity_conn =
+ clarity_instance.begin_block(tip, &next_block, &TEST_HEADER_DB, &burn_state_db);
+ clarity_conn.as_transaction(|tx| {
+ tx.with_clarity_db(|db| {
+ db.set_clarity_epoch_version(vote_state_epoch)?;
+ write_confirmed_cost_proposals(
+ db,
+ use_mainnet,
+ 0,
+ 1,
+ [cost_proposal(
+ boot_code_id("costs", use_mainnet),
+ "cost_le",
+ cost_definer.clone(),
+ "cost-definition-le",
+ )],
+ );
+ Ok(())
+ })
+ .unwrap();
+ });
+ clarity_conn.commit_block();
+ *tip = next_block;
+}
+
+fn tracker_with_voted_le_cost_state(
+ load_epoch: StacksEpochId,
+ vote_state_epoch: StacksEpochId,
+ use_mainnet: bool,
+) -> (LimitedCostTracker, QualifiedContractIdentifier) {
+ let (mut clarity_instance, mut tip, mut block_id_byte) =
+ new_cost_test_clarity_instance(use_mainnet);
+ let cost_definer = deploy_cost_voting_test_cost_definer(
+ &mut clarity_instance,
+ &mut tip,
+ &mut block_id_byte,
+ use_mainnet,
+ );
+
+ let max_setup_epoch = std::cmp::max(load_epoch, vote_state_epoch);
+ let mut vote_state_written = false;
+
+ for setup_epoch in COST_TEST_SETUP_EPOCHS {
+ if !vote_state_written && vote_state_epoch < setup_epoch {
+ write_voted_le_cost_state(
+ &mut clarity_instance,
+ &mut tip,
+ &mut block_id_byte,
+ vote_state_epoch,
+ use_mainnet,
+ &cost_definer,
+ );
+ vote_state_written = true;
+ }
+
+ if max_setup_epoch < setup_epoch {
+ break;
+ }
+
+ setup_cost_test_epoch(
+ &mut clarity_instance,
+ &mut tip,
+ &mut block_id_byte,
+ setup_epoch,
+ );
+ }
+
+ if !vote_state_written {
+ write_voted_le_cost_state(
+ &mut clarity_instance,
+ &mut tip,
+ &mut block_id_byte,
+ vote_state_epoch,
+ use_mainnet,
+ &cost_definer,
+ );
+ }
+
+ let mut marf_kv = clarity_instance.destroy();
+
+ let burn_state_db = generate_test_burn_state_db(load_epoch);
+ let final_block = next_test_block_id(&mut block_id_byte);
+ let mut store = marf_kv.begin(&tip, &final_block);
+ let owned_env = OwnedEnvironment::new_max_limit(
+ store.as_clarity_db(&TEST_HEADER_DB, &burn_state_db),
+ load_epoch,
+ use_mainnet,
+ );
+ let (_db, tracker) = owned_env.destruct().unwrap();
+
+ (tracker, cost_definer)
+}
+
fn exec_cost(contract: &str, use_mainnet: bool, epoch: StacksEpochId) -> ExecutionCost {
let p1 = execute("'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR");
let Value::Principal(PrincipalData::Standard(p1_principal)) = p1.clone() else {
@@ -1621,121 +1877,85 @@ fn test_cost_voting_integration(use_mainnet: bool, clarity_version: ClarityVersi
let bad_cases = vec![
// non existent "replacement target"
- (
- PrincipalData::from(QualifiedContractIdentifier::local("non-existent").unwrap()),
+ cost_proposal(
+ QualifiedContractIdentifier::local("non-existent").unwrap(),
"non-existent-func",
- PrincipalData::from(cost_definer.clone()),
+ cost_definer.clone(),
"cost-definition",
),
// replacement target isn't a contract principal
- (
- p1_principal.clone().into(),
+ cost_proposal(
+ p1_principal.clone(),
"non-existent-func",
- cost_definer.clone().into(),
+ cost_definer.clone(),
"cost-definition",
),
// cost defining contract isn't a contract principal
- (
- intercepted.clone().into(),
+ cost_proposal(
+ intercepted.clone(),
"intercepted-function",
- p1_principal.into(),
+ p1_principal,
"cost-definition",
),
// replacement function doesn't exist
- (
- intercepted.clone().into(),
+ cost_proposal(
+ intercepted.clone(),
"non-existent-func",
- cost_definer.clone().into(),
+ cost_definer.clone(),
"cost-definition",
),
// replacement function isn't read-only
- (
- intercepted.clone().into(),
+ cost_proposal(
+ intercepted.clone(),
"non-read-only",
- cost_definer.clone().into(),
+ cost_definer.clone(),
"cost-definition",
),
// "boot cost" function doesn't exist
- (
- boot_code_id("costs", false).into(),
+ cost_proposal(
+ boot_code_id("costs", false),
"non-existent-func",
- cost_definer.clone().into(),
+ cost_definer.clone(),
"cost-definition",
),
// cost defining contract doesn't exist
- (
- intercepted.clone().into(),
+ cost_proposal(
+ intercepted.clone(),
"intercepted-function",
- QualifiedContractIdentifier::local("non-existent")
- .unwrap()
- .into(),
+ QualifiedContractIdentifier::local("non-existent").unwrap(),
"cost-definition",
),
// cost defining function doesn't exist
- (
- intercepted.clone().into(),
+ cost_proposal(
+ intercepted.clone(),
"intercepted-function",
- cost_definer.clone().into(),
+ cost_definer.clone(),
"cost-definition-2",
),
// cost defining contract isn't arithmetic-only
- (
- intercepted.clone().into(),
+ cost_proposal(
+ intercepted.clone(),
"intercepted-function",
- bad_cost_definer.into(),
+ bad_cost_definer,
"cost-definition",
),
// cost defining contract has incorrect number of arguments
- (
- intercepted.clone().into(),
+ cost_proposal(
+ intercepted.clone(),
"intercepted-function",
- bad_cost_args_definer.into(),
+ bad_cost_args_definer,
"cost-definition",
),
];
let bad_proposals = bad_cases.len();
- let voting_contract_to_use: &QualifiedContractIdentifier = if use_mainnet {
- &COST_VOTING_MAINNET_CONTRACT
- } else {
- &COST_VOTING_TESTNET_CONTRACT
- };
-
{
let mut store = marf_kv.begin(&StacksBlockId([1; 32]), &StacksBlockId([2; 32]));
let mut db = store.as_clarity_db(&TEST_HEADER_DB, burn_db);
db.begin();
-
- db.set_variable_unknown_descriptor(
- voting_contract_to_use,
- "confirmed-proposal-count",
- Value::UInt(bad_proposals as u128),
- )
- .unwrap();
-
- for (ix, (intercepted_ct, intercepted_f, cost_ct, cost_f)) in
- bad_cases.into_iter().enumerate()
- {
- let value = format!(
- "{{ function-contract: '{},
- function-name: \"{}\",
- cost-function-contract: '{},
- cost-function-name: \"{}\",
- confirmed-height: u1 }}",
- intercepted_ct, intercepted_f, cost_ct, cost_f
- );
- let epoch = db.get_clarity_epoch_version().unwrap();
- db.set_entry_unknown_descriptor(
- voting_contract_to_use,
- "confirmed-proposals",
- execute(&format!("{{ confirmed-id: u{} }}", ix)),
- execute(&value),
- &epoch,
- )
- .unwrap();
- }
+ write_confirmed_cost_proposals(&mut db, use_mainnet, 0, bad_proposals, bad_cases);
db.commit().unwrap();
store.test_commit();
}
@@ -1778,19 +1998,19 @@ fn test_cost_voting_integration(use_mainnet: bool, clarity_version: ClarityVersi
};
let good_cases = vec![
- (
+ cost_proposal(
intercepted.clone(),
"intercepted-function",
cost_definer.clone(),
"cost-definition",
),
- (
+ cost_proposal(
boot_code_id("costs", use_mainnet),
"cost_le",
cost_definer.clone(),
"cost-definition-le",
),
- (
+ cost_proposal(
intercepted.clone(),
"intercepted-function2",
cost_definer.clone(),
@@ -1804,35 +2024,14 @@ fn test_cost_voting_integration(use_mainnet: bool, clarity_version: ClarityVersi
let mut db = store.as_clarity_db(&TEST_HEADER_DB, burn_db);
db.begin();
- let good_proposals = good_cases.len() as u128;
- db.set_variable_unknown_descriptor(
- voting_contract_to_use,
- "confirmed-proposal-count",
- Value::UInt(bad_proposals as u128 + good_proposals),
- )
- .unwrap();
-
- for (ix, (intercepted_ct, intercepted_f, cost_ct, cost_f)) in
- good_cases.into_iter().enumerate()
- {
- let value = format!(
- "{{ function-contract: '{},
- function-name: \"{}\",
- cost-function-contract: '{},
- cost-function-name: \"{}\",
- confirmed-height: u1 }}",
- intercepted_ct, intercepted_f, cost_ct, cost_f
- );
- let epoch = db.get_clarity_epoch_version().unwrap();
- db.set_entry_unknown_descriptor(
- voting_contract_to_use,
- "confirmed-proposals",
- execute(&format!("{{ confirmed-id: u{} }}", ix + bad_proposals)),
- execute(&value),
- &epoch,
- )
- .unwrap();
- }
+ let good_proposals = good_cases.len();
+ write_confirmed_cost_proposals(
+ &mut db,
+ use_mainnet,
+ bad_proposals,
+ bad_proposals + good_proposals,
+ good_cases,
+ );
db.commit().unwrap();
store.test_commit();
@@ -1916,6 +2115,74 @@ fn test_cost_voting_integration_testnet() {
test_cost_voting_integration(false, ClarityVersion::Clarity2);
}
+/// Before epoch 4.0, confirmed `.cost-voting` state can replace boot cost functions.
+#[rstest::rstest]
+#[case::mainnet(true)]
+#[case::testnet(false)]
+fn cost_voting_state_applies_before_epoch_40(#[case] use_mainnet: bool) {
+ let (tracker, cost_definer) = tracker_with_voted_le_cost_state(
+ StacksEpochId::Epoch34,
+ StacksEpochId::Epoch34,
+ use_mainnet,
+ );
+
+ let cost_function_references = tracker.cost_function_references();
+ assert_eq!(
+ cost_function_references.len(),
+ ClarityCostFunction::ALL.len(),
+ "all cost functions must have a reference before cost-voting retires (mainnet={use_mainnet})"
+ );
+
+ let le_reference = cost_function_references
+ .get(&ClarityCostFunction::Le)
+ .expect("cost_le must have a reference");
+ let ClarityCostFunctionEvaluator::Clarity(le_reference) = le_reference else {
+ panic!("voted cost_le should be evaluated in Clarity before Epoch 4.0");
+ };
+ assert_eq!(&le_reference.contract_id, &cost_definer);
+ assert_eq!(&le_reference.function_name, "cost-definition-le");
+}
+
+/// With `.cost-voting` disabled >= epoch 4.0, both existing and newly written voting state is
+/// ignored and every cost function resolves to the boot cost contract.
+#[rstest::rstest]
+#[case::mainnet_pre_40_voting_state(true, StacksEpochId::Epoch34)]
+#[case::testnet_pre_40_voting_state(false, StacksEpochId::Epoch34)]
+#[case::mainnet_post_40_voting_state(true, StacksEpochId::Epoch40)]
+#[case::testnet_post_40_voting_state(false, StacksEpochId::Epoch40)]
+fn cost_voting_state_is_ignored_at_epoch_40(
+ #[case] use_mainnet: bool,
+ #[case] vote_state_epoch: StacksEpochId,
+) {
+ let (tracker, _cost_definer) =
+ tracker_with_voted_le_cost_state(StacksEpochId::Epoch40, vote_state_epoch, use_mainnet);
+
+ let cost_function_references = tracker.cost_function_references();
+ assert_eq!(
+ cost_function_references.len(),
+ ClarityCostFunction::ALL.len(),
+ "all cost functions must have a reference after cost-voting retires (mainnet={use_mainnet}, vote_state_epoch={vote_state_epoch})"
+ );
+ assert!(
+ cost_function_references.values().all(|evaluator| matches!(
+ evaluator,
+ ClarityCostFunctionEvaluator::Default(_, _, DefaultVersion::Costs5)
+ )),
+ "all cost functions must use the epoch 4.0 boot cost contract after cost-voting retires (mainnet={use_mainnet}, vote_state_epoch={vote_state_epoch})"
+ );
+
+ let le_reference = cost_function_references
+ .get(&ClarityCostFunction::Le)
+ .expect("cost_le must have a reference");
+ assert!(
+ matches!(
+ le_reference,
+ ClarityCostFunctionEvaluator::Default(_, _, DefaultVersion::Costs5)
+ ),
+ "voted cost_le replacement must be ignored after cost-voting retires (mainnet={use_mainnet}, vote_state_epoch={vote_state_epoch})"
+ );
+}
+
#[test]
fn test_cost_change() {
let use_mainnet = false;
diff --git a/stackslib/src/net/api/tests/postblock_v3.rs b/stackslib/src/net/api/tests/postblock_v3.rs
index 34cce000517..a50883bdbc0 100644
--- a/stackslib/src/net/api/tests/postblock_v3.rs
+++ b/stackslib/src/net/api/tests/postblock_v3.rs
@@ -15,7 +15,6 @@
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
-use clarity::util::hash::{MerkleTree, Sha512Trunc256Sum};
use stacks_common::types::chainstate::StacksPrivateKey;
use stacks_common::types::StacksEpochId;
From e6d3954c1db1c1871be29239562f3a48462c5458 Mon Sep 17 00:00:00 2001
From: Cyle Witruk <236413682+cylewitruk-stacks@users.noreply.github.com>
Date: Mon, 22 Jun 2026 13:23:39 +0200
Subject: [PATCH 2/5] changelog.d entry for cost-voting pre-removal gating
---
changelog.d/7341-cost-voting-epoch-4-0-gate | 1 +
1 file changed, 1 insertion(+)
create mode 100644 changelog.d/7341-cost-voting-epoch-4-0-gate
diff --git a/changelog.d/7341-cost-voting-epoch-4-0-gate b/changelog.d/7341-cost-voting-epoch-4-0-gate
new file mode 100644
index 00000000000..abb315646b5
--- /dev/null
+++ b/changelog.d/7341-cost-voting-epoch-4-0-gate
@@ -0,0 +1 @@
+Added `.cost-voting` disablement gate beginning from epoch 4.0
From cfe5171b1b5303c1b0e1f4a59f183fd36fa03edf Mon Sep 17 00:00:00 2001
From: Cyle Witruk <236413682+cylewitruk-stacks@users.noreply.github.com>
Date: Mon, 22 Jun 2026 13:32:01 +0200
Subject: [PATCH 3/5] crc: typo
---
stacks-common/src/types/tests.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/stacks-common/src/types/tests.rs b/stacks-common/src/types/tests.rs
index 60de824bcd1..89bff743494 100644
--- a/stacks-common/src/types/tests.rs
+++ b/stacks-common/src/types/tests.rs
@@ -49,7 +49,7 @@ fn test_epoch_range_ext_iter() {
StacksEpochId::index_of(epoch)
}
- // Full range is effectively equalent to the ALL constant.
+ // Full range is effectively equivalent to the ALL constant.
assert_eq!((..).as_slice(), StacksEpochId::ALL);
// Start = inclusive, end = unbounded
From d6c41fac9efb1fdc91bfe86e5fa094914e75dabc Mon Sep 17 00:00:00 2001
From: Cyle Witruk <236413682+cylewitruk-stacks@users.noreply.github.com>
Date: Mon, 22 Jun 2026 15:27:26 +0200
Subject: [PATCH 4/5] fully remove .cost-voting and related functionality
---
.../src/vm/analysis/arithmetic_checker/mod.rs | 337 -------
.../vm/analysis/arithmetic_checker/tests.rs | 521 ----------
.../contract_interface_builder/mod.rs | 1 -
clarity/src/vm/analysis/mod.rs | 3 -
.../src/vm/analysis/type_checker/v2_05/mod.rs | 13 +-
.../src/vm/analysis/type_checker/v2_1/mod.rs | 9 -
clarity/src/vm/analysis/types.rs | 2 -
clarity/src/vm/ast/mod.rs | 12 +-
clarity/src/vm/contexts.rs | 37 -
clarity/src/vm/costs/mod.rs | 633 +-----------
clarity/src/vm/functions/database.rs | 28 +-
stacks-common/src/types/mod.rs | 6 -
stacks-common/src/types/tests.rs | 19 -
stacks-node/src/tests/neon_integrations.rs | 346 -------
.../chainstate/stacks/boot/contract_tests.rs | 693 +------------
stackslib/src/clarity_vm/tests/costs.rs | 945 +-----------------
16 files changed, 47 insertions(+), 3558 deletions(-)
delete mode 100644 clarity/src/vm/analysis/arithmetic_checker/mod.rs
delete mode 100644 clarity/src/vm/analysis/arithmetic_checker/tests.rs
diff --git a/clarity/src/vm/analysis/arithmetic_checker/mod.rs b/clarity/src/vm/analysis/arithmetic_checker/mod.rs
deleted file mode 100644
index 4f4a55c0960..00000000000
--- a/clarity/src/vm/analysis/arithmetic_checker/mod.rs
+++ /dev/null
@@ -1,337 +0,0 @@
-// Copyright (C) 2013-2020 Blocstack PBC, a public benefit corporation
-// Copyright (C) 2020 Stacks Open Internet Foundation
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-
-//! Static-analysis pass that decides whether a contract is eligible to be
-//! installed as a *cost-function* contract under the SIP-006 cost-voting
-//! system.
-//!
-//! A cost-function contract supplies a Clarity function that the VM invokes
-//! on every call to a native operation in order to compute that operation's
-//! runtime/read/write cost. Because such functions run inside the cost
-//! accounting path itself, they must be:
-//!
-//! - **Deterministic**: no chain-state reads (`block-height`, `tx-sender`,
-//! `contract-caller`, `chain-id`, ...), no `contract-call?`.
-//! - **Side-effect free**: no map/var/FT/NFT defines or mutations, no STX
-//! transfers, no asset mints or burns, no `print`.
-//! - **Bounded**: no `map`/`fold`/`filter`/list-cons or other iterating
-//! constructs whose work scales with input size — the cost function must
-//! itself be cheap and predictable.
-//! - **Trait-free**: traits introduce dynamic dispatch that the cost
-//! accountant cannot reason about statically.
-//! - **Cheap**: only a restricted, predictable, constant-time subset of
-//! native forms is allowed, including arithmetic/logic and certain simple
-//! expression-building constructs.
-//!
-//! The pass walks every top-level form and expression and rejects anything
-//! outside the set of allowed constructs. The result is stored in
-//! [`ContractAnalysis::is_cost_contract_eligible`]; the cost-voting
-//! machinery in `vm::costs` later refuses to adopt a proposal whose target
-//! contract is not eligible.
-
-use clarity_types::representations::ClarityName;
-
-pub use super::errors::{
- RuntimeCheckErrorKind, StaticCheckError, check_argument_count, check_arguments_at_least,
-};
-use crate::vm::ClarityVersion;
-use crate::vm::analysis::types::ContractAnalysis;
-use crate::vm::functions::NativeFunctions;
-use crate::vm::functions::define::{DefineFunctions, DefineFunctionsParsed};
-use crate::vm::representations::SymbolicExpression;
-use crate::vm::representations::SymbolicExpressionType::{
- Atom, AtomValue, Field, List, LiteralValue, TraitReference,
-};
-use crate::vm::variables::NativeVariables;
-
-#[cfg(test)]
-mod tests;
-
-///
-/// A static-analysis pass that checks whether or not
-/// a proposed cost-function defining contract is allowable.
-/// Cost-function defining contracts may not use contract-call,
-/// any database operations, traits, or iterating operations (e.g., list
-/// operations)
-///
-pub struct ArithmeticOnlyChecker<'a> {
- clarity_version: &'a ClarityVersion,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub enum Error {
- DefineTypeForbidden(DefineFunctions),
- VariableForbidden(NativeVariables),
- FunctionNotPermitted(NativeFunctions),
- TraitReferencesForbidden,
- UnexpectedContractStructure,
-}
-
-impl std::error::Error for Error {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- None
- }
-}
-
-impl std::fmt::Display for Error {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{self:?}")
- }
-}
-
-impl ArithmeticOnlyChecker<'_> {
- pub fn check_contract_cost_eligible(contract_analysis: &mut ContractAnalysis) {
- let is_eligible = ArithmeticOnlyChecker::run(contract_analysis).is_ok();
- contract_analysis.is_cost_contract_eligible = is_eligible;
- }
-
- pub fn run(contract_analysis: &ContractAnalysis) -> Result<(), Error> {
- let checker = ArithmeticOnlyChecker {
- clarity_version: &contract_analysis.clarity_version,
- };
- for exp in contract_analysis.expressions.iter() {
- checker.check_top_levels(exp)?;
- }
-
- Ok(())
- }
-
- fn check_define_function(
- &self,
- _signature: &[SymbolicExpression],
- body: &SymbolicExpression,
- ) -> Result<(), Error> {
- self.check_expression(body)
- }
-
- fn check_top_levels(&self, expr: &SymbolicExpression) -> Result<(), Error> {
- use crate::vm::functions::define::DefineFunctionsParsed::*;
- if let Some(define_type) = DefineFunctionsParsed::try_parse(expr)
- .map_err(|_| Error::UnexpectedContractStructure)?
- {
- match define_type {
- // The _arguments_ to constant defines must be checked to ensure that
- // any _evaluated arguments_ supplied to them are valid.
- Constant { value, .. } => self.check_expression(value),
- PrivateFunction { signature, body } => self.check_define_function(signature, body),
- ReadOnlyFunction { signature, body } => self.check_define_function(signature, body),
- PersistedVariable { .. } => Err(Error::DefineTypeForbidden(
- DefineFunctions::PersistedVariable,
- )),
- BoundedFungibleToken { .. } => {
- Err(Error::DefineTypeForbidden(DefineFunctions::FungibleToken))
- }
- PublicFunction { .. } => {
- Err(Error::DefineTypeForbidden(DefineFunctions::PublicFunction))
- }
- Map { .. } => Err(Error::DefineTypeForbidden(DefineFunctions::Map)),
- NonFungibleToken { .. } => Err(Error::DefineTypeForbidden(
- DefineFunctions::NonFungibleToken,
- )),
- UnboundedFungibleToken { .. } => {
- Err(Error::DefineTypeForbidden(DefineFunctions::FungibleToken))
- }
- Trait { .. } => Err(Error::DefineTypeForbidden(DefineFunctions::Trait)),
- UseTrait { .. } => Err(Error::DefineTypeForbidden(DefineFunctions::UseTrait)),
- ImplTrait { .. } => Err(Error::DefineTypeForbidden(DefineFunctions::ImplTrait)),
- }
- } else {
- self.check_expression(expr)
- }
- }
-
- fn check_expression(&self, expr: &SymbolicExpression) -> Result<(), Error> {
- match expr.expr {
- AtomValue(_) | LiteralValue(_) => {
- // values and literals are always allowed
- Ok(())
- }
- Atom(ref variable) => self.check_variables_allowed(variable),
- Field(_) | TraitReference(_, _) => Err(Error::TraitReferencesForbidden),
- List(ref expression) => self.check_function_application(expression),
- }
- }
-
- fn check_variables_allowed(&self, var_name: &ClarityName) -> Result<(), Error> {
- use crate::vm::variables::NativeVariables::*;
- if let Some(native_var) =
- NativeVariables::lookup_by_name_at_version(var_name, self.clarity_version)
- {
- match native_var {
- ContractCaller | TxSender | TotalLiquidMicroSTX | BlockHeight | BurnBlockHeight
- | Regtest | TxSponsor | Mainnet | ChainId | StacksBlockHeight | TenureHeight
- | StacksBlockTime | CurrentContract => Err(Error::VariableForbidden(native_var)),
- NativeNone | NativeTrue | NativeFalse => Ok(()),
- }
- } else {
- Ok(())
- }
- }
-
- fn try_native_function_check(
- &self,
- function: &str,
- args: &[SymbolicExpression],
- ) -> Option> {
- NativeFunctions::lookup_by_name_at_version(function, self.clarity_version)
- .map(|function| self.check_native_function(function, args))
- }
-
- fn check_native_function(
- &self,
- function: NativeFunctions,
- args: &[SymbolicExpression],
- ) -> Result<(), Error> {
- use crate::vm::functions::NativeFunctions::*;
- match function {
- FetchVar | GetBlockInfo | GetBurnBlockInfo | GetStacksBlockInfo | GetTenureInfo
- | GetTokenBalance | GetAssetOwner | FetchEntry | SetEntry | DeleteEntry
- | InsertEntry | SetVar | MintAsset | MintToken | TransferAsset | TransferToken
- | ContractCall | StxTransfer | StxTransferMemo | StxBurn | AtBlock | GetStxBalance
- | GetTokenSupply | BurnToken | FromConsensusBuff | ToConsensusBuff | BurnAsset
- | StxGetAccount => Err(Error::FunctionNotPermitted(function)),
- Append
- | Concat
- | AsMaxLen
- | ContractOf
- | PrincipalOf
- | ListCons
- | Print
- | AsContract
- | ElementAt
- | ElementAtAlias
- | IndexOf
- | IndexOfAlias
- | Map
- | Filter
- | Fold
- | Slice
- | ReplaceAt
- | ContractHash
- | RestrictAssets
- | AsContractSafe
- | AllowanceWithStx
- | AllowanceWithFt
- | AllowanceWithNft
- | AllowanceWithStacking
- | AllowanceWithStaking
- | AllowanceWithPox
- | AllowanceAll => Err(Error::FunctionNotPermitted(function)),
- BuffToIntLe | BuffToUIntLe | BuffToIntBe | BuffToUIntBe => {
- Err(Error::FunctionNotPermitted(function))
- }
- IsStandard | PrincipalDestruct | PrincipalConstruct => {
- Err(Error::FunctionNotPermitted(function))
- }
- IntToAscii | IntToUtf8 | StringToInt | StringToUInt | ToAscii => {
- Err(Error::FunctionNotPermitted(function))
- }
- Sha512 | Sha512Trunc256 | Secp256k1Recover | Secp256k1Verify | Secp256r1Verify
- | Ed25519Verify | Secp256k1Decompress | Hash160 | Sha256 | Keccak256
- | VerifyMerkleProof | GetBitcoinTxOutput => Err(Error::FunctionNotPermitted(function)),
- Add | Subtract | Divide | Multiply | CmpGeq | CmpLeq | CmpLess | CmpGreater
- | Modulo | Power | Sqrti | Log2 | BitwiseXor | And | Or | Not | Equals | If
- | ConsSome | ConsOkay | ConsError | DefaultTo | UnwrapRet | UnwrapErrRet | IsOkay
- | IsNone | Asserts | Unwrap | UnwrapErr | IsErr | IsSome | TryRet | ToUInt | ToInt
- | Len | Begin | TupleMerge | BitwiseOr | BitwiseAnd | BitwiseXor2 | BitwiseNot
- | BitwiseLShift | BitwiseRShift => {
- // Check all arguments.
- self.check_all(args)
- }
- // we need to treat all the remaining functions specially, because these
- // do not eval all of their arguments (rather, one or more of their arguments
- // is a name)
- TupleGet => {
- // these functions use a name in the first argument
- check_argument_count(2, args).map_err(|_| Error::UnexpectedContractStructure)?;
- self.check_all(&args[1..])
- }
- Match => {
- if !(args.len() == 4 || args.len() == 5) {
- return Err(Error::UnexpectedContractStructure);
- }
- // check the match input
- self.check_expression(&args[0])?;
- // check the 'ok' branch
- self.check_expression(&args[2])?;
- // check the 'err' branch
- if args.len() == 4 {
- self.check_expression(&args[3])
- } else {
- self.check_expression(&args[4])
- }
- }
- Let => {
- check_arguments_at_least(2, args)
- .map_err(|_| Error::UnexpectedContractStructure)?;
-
- let binding_list = args[0]
- .match_list()
- .ok_or(Error::UnexpectedContractStructure)?;
-
- for pair in binding_list.iter() {
- let pair_expression = pair
- .match_list()
- .ok_or(Error::UnexpectedContractStructure)?;
- if pair_expression.len() != 2 {
- return Err(Error::UnexpectedContractStructure);
- }
-
- self.check_expression(&pair_expression[1])?;
- }
-
- self.check_all(&args[1..args.len()])
- }
- TupleCons => {
- for pair in args.iter() {
- let pair_expression = pair
- .match_list()
- .ok_or(Error::UnexpectedContractStructure)?;
- if pair_expression.len() != 2 {
- return Err(Error::UnexpectedContractStructure);
- }
-
- self.check_expression(&pair_expression[1])?;
- }
- Ok(())
- }
- }
- }
-
- fn check_all(&self, expressions: &[SymbolicExpression]) -> Result<(), Error> {
- for expr in expressions.iter() {
- self.check_expression(expr)?;
- }
- Ok(())
- }
-
- fn check_function_application(&self, expression: &[SymbolicExpression]) -> Result<(), Error> {
- let (function_name, args) = expression
- .split_first()
- .ok_or(Error::UnexpectedContractStructure)?;
-
- let function_name = function_name
- .match_atom()
- .ok_or(Error::UnexpectedContractStructure)?;
-
- if let Some(result) = self.try_native_function_check(function_name, args) {
- result
- } else {
- // non-native function invocations are always okay, just check the args!
- self.check_all(args)
- }
- }
-}
diff --git a/clarity/src/vm/analysis/arithmetic_checker/tests.rs b/clarity/src/vm/analysis/arithmetic_checker/tests.rs
deleted file mode 100644
index 0b0c33fe372..00000000000
--- a/clarity/src/vm/analysis/arithmetic_checker/tests.rs
+++ /dev/null
@@ -1,521 +0,0 @@
-// Copyright (C) 2013-2020 Blocstack PBC, a public benefit corporation
-// Copyright (C) 2020-2026 Stacks Open Internet Foundation
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-
-#[cfg(test)]
-use rstest::rstest;
-#[cfg(test)]
-use rstest_reuse::{self, *};
-use stacks_common::types::StacksEpochId;
-
-use crate::vm::ClarityVersion;
-use crate::vm::analysis::ContractAnalysis;
-use crate::vm::analysis::arithmetic_checker::Error::*;
-use crate::vm::analysis::arithmetic_checker::{ArithmeticOnlyChecker, Error};
-use crate::vm::ast::parse;
-use crate::vm::costs::LimitedCostTracker;
-use crate::vm::functions::NativeFunctions;
-use crate::vm::functions::define::DefineFunctions;
-use crate::vm::tests::test_clarity_versions;
-use crate::vm::tooling::mem_type_check;
-use crate::vm::types::QualifiedContractIdentifier;
-use crate::vm::variables::NativeVariables;
-
-/// Checks whether or not a contract only contains arithmetic expressions (for example, defining a
-/// map would not pass this check).
-/// This check is useful in determining the validity of new potential cost functions.
-fn arithmetic_check(
- contract: &str,
- version: ClarityVersion,
- epoch: StacksEpochId,
-) -> Result<(), Error> {
- let contract_identifier = QualifiedContractIdentifier::transient();
- let expressions = parse(&contract_identifier, contract, version, epoch).unwrap();
-
- let analysis = ContractAnalysis::new(
- contract_identifier,
- expressions,
- LimitedCostTracker::new_free(),
- epoch,
- version,
- );
-
- ArithmeticOnlyChecker::run(&analysis)
-}
-
-fn check_good(contract: &str, version: ClarityVersion, epoch: StacksEpochId) {
- let analysis = mem_type_check(contract, version, epoch).unwrap().1;
- ArithmeticOnlyChecker::run(&analysis).expect("Should pass arithmetic checks");
-}
-
-#[apply(test_clarity_versions)]
-fn test_bad_defines(#[case] version: ClarityVersion, #[case] epoch: StacksEpochId) {
- let tests = [
- (
- "(define-public (foo) (ok 1))",
- DefineTypeForbidden(DefineFunctions::PublicFunction),
- ),
- (
- "(define-map foo-map ((a uint)) ((b uint))) (define-private (foo) (map-get? foo-map {a: u1}))",
- DefineTypeForbidden(DefineFunctions::Map),
- ),
- (
- "(define-data-var foo-var uint u1) (define-private (foo) (var-get foo-var))",
- DefineTypeForbidden(DefineFunctions::PersistedVariable),
- ),
- (
- "(define-fungible-token tokaroos u500)",
- DefineTypeForbidden(DefineFunctions::FungibleToken),
- ),
- (
- "(define-fungible-token tokaroos)",
- DefineTypeForbidden(DefineFunctions::FungibleToken),
- ),
- (
- "(define-non-fungible-token tokaroos uint)",
- DefineTypeForbidden(DefineFunctions::NonFungibleToken),
- ),
- (
- "(define-trait foo-trait ((foo (uint)) (response uint uint)))",
- DefineTypeForbidden(DefineFunctions::Trait),
- ),
- ];
-
- // Check bad defines for each clarity version
- for (contract, error) in tests.iter() {
- assert_eq!(
- arithmetic_check(contract, version, epoch),
- Err(error.clone()),
- "Check contract:\n {contract}"
- );
- }
-}
-
-#[test]
-fn test_variables_fail_arithmetic_check_clarity1() {
- // Tests the behavior using Clarity1.
- let tests = [
- (
- "(define-private (foo) burn-block-height)",
- Err(VariableForbidden(NativeVariables::BurnBlockHeight)),
- ),
- (
- "(define-private (foo) block-height)",
- Err(VariableForbidden(NativeVariables::BlockHeight)),
- ),
- (
- "(define-private (foo) tx-sender)",
- Err(VariableForbidden(NativeVariables::TxSender)),
- ),
- (
- "(define-private (foo) contract-caller)",
- Err(VariableForbidden(NativeVariables::ContractCaller)),
- ),
- (
- "(define-private (foo) is-in-regtest)",
- Err(VariableForbidden(NativeVariables::Regtest)),
- ),
- (
- "(define-private (foo) stx-liquid-supply)",
- Err(VariableForbidden(NativeVariables::TotalLiquidMicroSTX)),
- ),
- ("(define-private (foo) tx-sponsor?)", Ok(())),
- ("(define-private (foo) is-in-mainnet)", Ok(())),
- ("(define-private (foo) chain-id)", Ok(())),
- ];
-
- for (contract, result) in tests.iter() {
- assert_eq!(
- arithmetic_check(contract, ClarityVersion::Clarity1, StacksEpochId::Epoch2_05),
- result.clone(),
- "Check contract:\n {contract}"
- );
- assert_eq!(
- arithmetic_check(contract, ClarityVersion::Clarity1, StacksEpochId::Epoch21),
- result.clone(),
- "Check contract:\n {contract}"
- );
- }
-
- let tests = [
- "(define-private (foo) (begin true false none))",
- "(define-private (foo) 1)",
- ];
-
- for contract in tests.iter() {
- check_good(contract, ClarityVersion::Clarity1, StacksEpochId::Epoch2_05);
- check_good(contract, ClarityVersion::Clarity1, StacksEpochId::Epoch21);
- }
-}
-
-#[test]
-fn test_variables_fail_arithmetic_check_clarity2() {
- // Tests the behavior using Clarity2.
- let tests = [
- (
- "(define-private (foo) burn-block-height)",
- Err(VariableForbidden(NativeVariables::BurnBlockHeight)),
- ),
- (
- "(define-private (foo) block-height)",
- Err(VariableForbidden(NativeVariables::BlockHeight)),
- ),
- (
- "(define-private (foo) tx-sender)",
- Err(VariableForbidden(NativeVariables::TxSender)),
- ),
- (
- "(define-private (foo) contract-caller)",
- Err(VariableForbidden(NativeVariables::ContractCaller)),
- ),
- (
- "(define-private (foo) is-in-regtest)",
- Err(VariableForbidden(NativeVariables::Regtest)),
- ),
- (
- "(define-private (foo) stx-liquid-supply)",
- Err(VariableForbidden(NativeVariables::TotalLiquidMicroSTX)),
- ),
- (
- "(define-private (foo) tx-sponsor?)",
- Err(VariableForbidden(NativeVariables::TxSponsor)),
- ),
- (
- "(define-private (foo) is-in-mainnet)",
- Err(VariableForbidden(NativeVariables::Mainnet)),
- ),
- (
- "(define-private (foo) chain-id)",
- Err(VariableForbidden(NativeVariables::ChainId)),
- ),
- ];
-
- for (contract, result) in tests.iter() {
- assert_eq!(
- arithmetic_check(contract, ClarityVersion::Clarity2, StacksEpochId::Epoch21),
- result.clone(),
- "Check contract:\n {contract}"
- );
- }
-}
-
-#[test]
-fn test_functions_clarity1() {
- // Tests all functions against Clarity1 VM. Results should be different for Clarity1 vs Clarity2 functions.
- let tests = [
- // Clarity1 functions.
- ("(define-private (foo) (at-block 0x0202020202020202020202020202020202020202020202020202020202020202 (+ 1 2)))",
- Err(FunctionNotPermitted(NativeFunctions::AtBlock))),
- ("(define-private (foo) (map-get? foo-map {a: u1}))",
- Err(FunctionNotPermitted(NativeFunctions::FetchEntry))),
- ("(define-private (foo) (map-delete foo-map {a: u1}))",
- Err(FunctionNotPermitted(NativeFunctions::DeleteEntry))),
- ("(define-private (foo) (map-set foo-map {a: u1} {b: u2}))",
- Err(FunctionNotPermitted(NativeFunctions::SetEntry))),
- ("(define-private (foo) (map-insert foo-map {a: u1} {b: u2}))",
- Err(FunctionNotPermitted(NativeFunctions::InsertEntry))),
- ("(define-private (foo) (var-get foo-var))",
- Err(FunctionNotPermitted(NativeFunctions::FetchVar))),
- ("(define-private (foo) (var-set foo-var u2))",
- Err(FunctionNotPermitted(NativeFunctions::SetVar))),
- ("(define-private (foo (a principal)) (ft-get-balance tokaroos a))",
- Err(FunctionNotPermitted(NativeFunctions::GetTokenBalance))),
- ("(define-private (foo (a principal))
- (ft-transfer? stackaroo u50 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF))",
- Err(FunctionNotPermitted(NativeFunctions::TransferToken))),
- ("(define-private (foo (a principal))
- (ft-mint? stackaroo u100 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR))",
- Err(FunctionNotPermitted(NativeFunctions::MintToken))),
- ("(define-private (foo (a principal))
- (nft-mint? stackaroo \"Roo\" 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR))",
- Err(FunctionNotPermitted(NativeFunctions::MintAsset))),
- ("(nft-transfer? stackaroo \"Roo\" 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF)",
- Err(FunctionNotPermitted(NativeFunctions::TransferAsset))),
- ("(nft-get-owner? stackaroo \"Roo\")",
- Err(FunctionNotPermitted(NativeFunctions::GetAssetOwner))),
- ("(get-block-info? id-header-hash 0)",
- Err(FunctionNotPermitted(NativeFunctions::GetBlockInfo))),
- ("(define-private (foo) (contract-call? .bar outer-call))",
- Err(FunctionNotPermitted(NativeFunctions::ContractCall))),
- ("(stx-get-balance 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF)",
- Err(FunctionNotPermitted(NativeFunctions::GetStxBalance))),
- ("(stx-burn? u100 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF)",
- Err(FunctionNotPermitted(NativeFunctions::StxBurn))),
- (r#"(stx-transfer? u100 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF)"#,
- Err(FunctionNotPermitted(NativeFunctions::StxTransfer))),
- ("(define-private (foo (a (list 3 uint)))
- (map log2 a))",
- Err(FunctionNotPermitted(NativeFunctions::Map))),
- ("(define-private (foo (a (list 3 (optional uint))))
- (filter is-none a))",
- Err(FunctionNotPermitted(NativeFunctions::Filter))),
- ("(define-private (foo (a (list 3 uint)))
- (append a u4))",
- Err(FunctionNotPermitted(NativeFunctions::Append))),
- ("(define-private (foo (a (list 3 uint)))
- (concat a a))",
- Err(FunctionNotPermitted(NativeFunctions::Concat))),
- ("(define-private (foo (a (list 3 uint)))
- (as-max-len? a u4))",
- Err(FunctionNotPermitted(NativeFunctions::AsMaxLen))),
- ("(define-private (foo) (print 10))",
- Err(FunctionNotPermitted(NativeFunctions::Print))),
- ("(define-private (foo) (list 3 4 10))",
- Err(FunctionNotPermitted(NativeFunctions::ListCons))),
- ("(define-private (foo) (keccak256 0))",
- Err(FunctionNotPermitted(NativeFunctions::Keccak256))),
- ("(define-private (foo) (hash160 0))",
- Err(FunctionNotPermitted(NativeFunctions::Hash160))),
- ("(define-private (foo) (secp256k1-recover? 0xde5b9eb9e7c5592930eb2e30a01369c36586d872082ed8181ee83d2a0ec20f04 0x8738487ebe69b93d8e51583be8eee50bb4213fc49c767d329632730cc193b873554428fc936ca3569afc15f1c9365f6591d6251a89fee9c9ac661116824d3a1301))",
- Err(FunctionNotPermitted(NativeFunctions::Secp256k1Recover))),
- ("(define-private (foo) (secp256k1-verify 0xde5b9eb9e7c5592930eb2e30a01369c36586d872082ed8181ee83d2a0ec20f04
- 0x8738487ebe69b93d8e51583be8eee50bb4213fc49c767d329632730cc193b873554428fc936ca3569afc15f1c9365f6591d6251a89fee9c9ac661116824d3a13
- 0x03adb8de4bfb65db2cfd6120d55c6526ae9c52e675db7e47308636534ba7786110))",
- Err(FunctionNotPermitted(NativeFunctions::Secp256k1Verify))),
- ("(define-private (foo) (sha256 0))",
- Err(FunctionNotPermitted(NativeFunctions::Sha256))),
- ("(define-private (foo) (sha512 0))",
- Err(FunctionNotPermitted(NativeFunctions::Sha512))),
- ("(define-private (foo) (sha512/256 0))",
- Err(FunctionNotPermitted(NativeFunctions::Sha512Trunc256))),
-
- // Clarity2 functions.
- (r#"(stx-transfer-memo? u100 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF 0x010203)"#,
- Ok(())),
- ("(define-private (foo (a (list 3 uint)))
- (slice? a u2 u3))",
- Ok(())),
- ("(define-private (foo (a (list 3 uint)) (b uint))
- (replace-at? a u1 b))",
- Ok(())),
- ("(buff-to-int-le 0x0001)",
- Ok(())),
- ("(buff-to-uint-le 0x0001)",
- Ok(())),
- ("(buff-to-int-be 0x0001)",
- Ok(())),
- ("(buff-to-uint-be 0x0001)",
- Ok(())),
- ("(buff-to-uint-be 0x0001)",
- Ok(())),
- ("(is-standard 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6)",
- Ok(())),
- ("(principal-destruct? 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6)",
- Ok(())),
- ("(principal-construct? 0x22 0xfa6bf38ed557fe417333710d6033e9419391a320)",
- Ok(())),
- ("(string-to-int? \"-1\")",
- Ok(())),
- ("(string-to-uint? \"1\")",
- Ok(())),
- ("(int-to-ascii 5)",
- Ok(())),
- ("(int-to-utf8 10)",
- Ok(())),
- ("(get-burn-block-info? header-hash u0)",
- Ok(())),
- ("(stx-account 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR)",
- Ok(())),
- ("(to-consensus-buff? 3)",
- Ok(())),
- ("(from-consensus-buff? true 0x03)",
- Ok(())),
- ];
-
- for (contract, result) in tests.iter() {
- assert_eq!(
- arithmetic_check(contract, ClarityVersion::Clarity1, StacksEpochId::Epoch2_05),
- result.clone(),
- "Check contract:\n {contract}"
- );
- assert_eq!(
- arithmetic_check(contract, ClarityVersion::Clarity1, StacksEpochId::Epoch21),
- result.clone(),
- "Check contract:\n {contract}"
- );
- }
-}
-
-#[test]
-fn test_functions_clarity2() {
- // Tests functions against Clarity2 VM. The Clarity1 functions should still cause an error.
- let tests = [
- // Clarity2 functions.
- (r#"(stx-transfer-memo? u100 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF 0x010203)"#,
- Err(FunctionNotPermitted(NativeFunctions::StxTransferMemo))),
- ("(define-private (foo (a (list 3 uint)))
- (slice? a u2 u3))",
- Err(FunctionNotPermitted(NativeFunctions::Slice))),
- ("(define-private (foo (a (list 3 uint)))
- (replace-at? a u2 (list u3)))",
- Err(FunctionNotPermitted(NativeFunctions::ReplaceAt))),
- ("(buff-to-int-le 0x0001)",
- Err(FunctionNotPermitted(NativeFunctions::BuffToIntLe))),
- ("(buff-to-uint-le 0x0001)",
- Err(FunctionNotPermitted(NativeFunctions::BuffToUIntLe))),
- ("(buff-to-int-be 0x0001)",
- Err(FunctionNotPermitted(NativeFunctions::BuffToIntBe))),
- ("(buff-to-uint-be 0x0001)",
- Err(FunctionNotPermitted(NativeFunctions::BuffToUIntBe))),
- ("(is-standard 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6)",
- Err(FunctionNotPermitted(NativeFunctions::IsStandard))),
- ("(principal-destruct? 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6)",
- Err(FunctionNotPermitted(NativeFunctions::PrincipalDestruct))),
- ("(principal-construct? 0x22 0xfa6bf38ed557fe417333710d6033e9419391a320)",
- Err(FunctionNotPermitted(NativeFunctions::PrincipalConstruct))),
- ("(string-to-int? \"-1\")",
- Err(FunctionNotPermitted(NativeFunctions::StringToInt))),
- ("(string-to-uint? \"1\")",
- Err(FunctionNotPermitted(NativeFunctions::StringToUInt))),
- ("(int-to-ascii 5)",
- Err(FunctionNotPermitted(NativeFunctions::IntToAscii))),
- ("(int-to-utf8 10)",
- Err(FunctionNotPermitted(NativeFunctions::IntToUtf8))),
- ("(get-burn-block-info? header-hash u0)",
- Err(FunctionNotPermitted(NativeFunctions::GetBurnBlockInfo))),
- ("(stx-account 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR)",
- Err(FunctionNotPermitted(NativeFunctions::StxGetAccount))),
- ("(to-consensus-buff? 3)",
- Err(FunctionNotPermitted(NativeFunctions::ToConsensusBuff))),
- ("(from-consensus-buff? true 0x03)",
- Err(FunctionNotPermitted(NativeFunctions::FromConsensusBuff))),
-
- // Clarity1 functions.
- ("(define-private (foo) (at-block 0x0202020202020202020202020202020202020202020202020202020202020202 (+ 1 2)))",
- Err(FunctionNotPermitted(NativeFunctions::AtBlock))),
- ("(define-private (foo) (map-get? foo-map {a: u1}))",
- Err(FunctionNotPermitted(NativeFunctions::FetchEntry))),
- ("(define-private (foo) (map-delete foo-map {a: u1}))",
- Err(FunctionNotPermitted(NativeFunctions::DeleteEntry))),
- ("(define-private (foo) (map-set foo-map {a: u1} {b: u2}))",
- Err(FunctionNotPermitted(NativeFunctions::SetEntry))),
- ("(define-private (foo) (map-insert foo-map {a: u1} {b: u2}))",
- Err(FunctionNotPermitted(NativeFunctions::InsertEntry))),
- ("(define-private (foo) (var-get foo-var))",
- Err(FunctionNotPermitted(NativeFunctions::FetchVar))),
- ("(define-private (foo) (var-set foo-var u2))",
- Err(FunctionNotPermitted(NativeFunctions::SetVar))),
- ("(define-private (foo (a principal)) (ft-get-balance tokaroos a))",
- Err(FunctionNotPermitted(NativeFunctions::GetTokenBalance))),
- ("(define-private (foo (a principal))
- (ft-transfer? stackaroo u50 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF))",
- Err(FunctionNotPermitted(NativeFunctions::TransferToken))),
- ("(define-private (foo (a principal))
- (ft-mint? stackaroo u100 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR))",
- Err(FunctionNotPermitted(NativeFunctions::MintToken))),
- ("(define-private (foo (a principal))
- (nft-mint? stackaroo \"Roo\" 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR))",
- Err(FunctionNotPermitted(NativeFunctions::MintAsset))),
- ("(nft-transfer? stackaroo \"Roo\" 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF)",
- Err(FunctionNotPermitted(NativeFunctions::TransferAsset))),
- ("(nft-get-owner? stackaroo \"Roo\")",
- Err(FunctionNotPermitted(NativeFunctions::GetAssetOwner))),
- ("(get-block-info? id-header-hash 0)",
- Err(FunctionNotPermitted(NativeFunctions::GetBlockInfo))),
- ("(define-private (foo) (contract-call? .bar outer-call))",
- Err(FunctionNotPermitted(NativeFunctions::ContractCall))),
- ("(stx-get-balance 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF)",
- Err(FunctionNotPermitted(NativeFunctions::GetStxBalance))),
- ("(stx-burn? u100 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF)",
- Err(FunctionNotPermitted(NativeFunctions::StxBurn))),
- (r#"(stx-transfer? u100 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF)"#,
- Err(FunctionNotPermitted(NativeFunctions::StxTransfer))),
- ("(define-private (foo (a (list 3 uint)))
- (map log2 a))",
- Err(FunctionNotPermitted(NativeFunctions::Map))),
- ("(define-private (foo (a (list 3 (optional uint))))
- (filter is-none a))",
- Err(FunctionNotPermitted(NativeFunctions::Filter))),
- ("(define-private (foo (a (list 3 uint)))
- (append a u4))",
- Err(FunctionNotPermitted(NativeFunctions::Append))),
- ("(define-private (foo (a (list 3 uint)))
- (concat a a))",
- Err(FunctionNotPermitted(NativeFunctions::Concat))),
- ("(define-private (foo (a (list 3 uint)))
- (as-max-len? a u4))",
- Err(FunctionNotPermitted(NativeFunctions::AsMaxLen))),
- ("(define-private (foo) (print 10))",
- Err(FunctionNotPermitted(NativeFunctions::Print))),
- ("(define-private (foo) (list 3 4 10))",
- Err(FunctionNotPermitted(NativeFunctions::ListCons))),
- ("(define-private (foo) (keccak256 0))",
- Err(FunctionNotPermitted(NativeFunctions::Keccak256))),
- ("(define-private (foo) (hash160 0))",
- Err(FunctionNotPermitted(NativeFunctions::Hash160))),
- ("(define-private (foo) (secp256k1-recover? 0xde5b9eb9e7c5592930eb2e30a01369c36586d872082ed8181ee83d2a0ec20f04 0x8738487ebe69b93d8e51583be8eee50bb4213fc49c767d329632730cc193b873554428fc936ca3569afc15f1c9365f6591d6251a89fee9c9ac661116824d3a1301))",
- Err(FunctionNotPermitted(NativeFunctions::Secp256k1Recover))),
- ("(define-private (foo) (secp256k1-verify 0xde5b9eb9e7c5592930eb2e30a01369c36586d872082ed8181ee83d2a0ec20f04
- 0x8738487ebe69b93d8e51583be8eee50bb4213fc49c767d329632730cc193b873554428fc936ca3569afc15f1c9365f6591d6251a89fee9c9ac661116824d3a13
- 0x03adb8de4bfb65db2cfd6120d55c6526ae9c52e675db7e47308636534ba7786110))",
- Err(FunctionNotPermitted(NativeFunctions::Secp256k1Verify))),
- ("(define-private (foo) (sha256 0))",
- Err(FunctionNotPermitted(NativeFunctions::Sha256))),
- ("(define-private (foo) (sha512 0))",
- Err(FunctionNotPermitted(NativeFunctions::Sha512))),
- ("(define-private (foo) (sha512/256 0))",
- Err(FunctionNotPermitted(NativeFunctions::Sha512Trunc256))),
- ];
-
- for (contract, result) in tests.iter() {
- assert_eq!(
- arithmetic_check(contract, ClarityVersion::Clarity2, StacksEpochId::Epoch21),
- result.clone(),
- "Check contract:\n {contract}"
- );
- }
-}
-
-#[test]
-fn test_functions_contract() {
- let good_tests = [
- "(match (if (is-eq 0 1) (ok 1) (err 2))
- ok-val (+ 1 ok-val)
- err-val (+ 2 err-val))",
- "(match (if (is-eq 0 1) (some 1) none)
- ok-val (+ 1 ok-val)
- 2)",
- "(get a { a: (+ 9 1), b: (if (> 2 3) (* 4 4) (/ 4 2)) })",
- "(define-private (foo)
- (let ((a (+ 3 2 3))) (log2 a)))",
- "(define-private (foo)
- (let ((a (+ 32 3 4)) (b (- 32 1))
- (c (if (and (< 3 2) (<= 3 2) (>= 4 5) (or (is-eq (mod 5 4) 0) (not (> 3 2))))
- (pow u2 (log2 (sqrti u100000)))
- (xor u120 u280)))
- (d (default-to u0 (some u2))))
- (begin (unwrap! (some u3) u1)
- (unwrap-err! (err u5) u4)
- (asserts! true u4)
- (unwrap-panic (some u3))
- (unwrap-err-panic (err u5)))))",
- "(define-private (foo) (to-int (to-uint 34)))
- (define-private (bar) (foo))",
- "(define-private (foo) (begin
- (is-some (some 4))
- (is-none (some 4))
- (is-ok (ok 4))
- (is-err (ok 5))
- (try! (some 4))
- (some 5)))
- (define-read-only (bar) (foo))",
- ];
-
- for contract in good_tests.iter() {
- check_good(contract, ClarityVersion::Clarity1, StacksEpochId::Epoch2_05);
- check_good(contract, ClarityVersion::Clarity1, StacksEpochId::Epoch21);
- check_good(contract, ClarityVersion::Clarity2, StacksEpochId::Epoch21);
- }
-}
diff --git a/clarity/src/vm/analysis/contract_interface_builder/mod.rs b/clarity/src/vm/analysis/contract_interface_builder/mod.rs
index 89450c48864..3b1774571b0 100644
--- a/clarity/src/vm/analysis/contract_interface_builder/mod.rs
+++ b/clarity/src/vm/analysis/contract_interface_builder/mod.rs
@@ -50,7 +50,6 @@ pub fn build_contract_interface(
type_map: _,
cost_track: _,
contract_interface: _,
- is_cost_contract_eligible: _,
} = contract_analysis;
contract_interface
diff --git a/clarity/src/vm/analysis/mod.rs b/clarity/src/vm/analysis/mod.rs
index 1b92af2b0b1..350fc8f02a2 100644
--- a/clarity/src/vm/analysis/mod.rs
+++ b/clarity/src/vm/analysis/mod.rs
@@ -15,7 +15,6 @@
// along with this program. If not, see .
pub mod analysis_db;
-pub mod arithmetic_checker;
pub mod contract_interface_builder;
pub mod errors;
pub mod read_only_checker;
@@ -26,7 +25,6 @@ pub mod types;
use stacks_common::types::StacksEpochId;
pub use self::analysis_db::AnalysisDatabase;
-use self::arithmetic_checker::ArithmeticOnlyChecker;
use self::contract_interface_builder::build_contract_interface;
pub use self::errors::{
CommonCheckErrorKind, RuntimeCheckErrorKind, StaticCheckError, StaticCheckErrorKind,
@@ -142,7 +140,6 @@ pub fn run_analysis(
TypeChecker2_05::run_pass(&epoch, &mut contract_analysis, db, build_type_map)?;
}
TraitChecker::run_pass(&epoch, &mut contract_analysis, db)?;
- ArithmeticOnlyChecker::check_contract_cost_eligible(&mut contract_analysis);
if STORE_CONTRACT_SRC_INTERFACE {
let interface = build_contract_interface(&contract_analysis)?;
diff --git a/clarity/src/vm/analysis/type_checker/v2_05/mod.rs b/clarity/src/vm/analysis/type_checker/v2_05/mod.rs
index e32c12e7d4d..365c6a4dc43 100644
--- a/clarity/src/vm/analysis/type_checker/v2_05/mod.rs
+++ b/clarity/src/vm/analysis/type_checker/v2_05/mod.rs
@@ -45,8 +45,8 @@ use crate::vm::representations::SymbolicExpressionType::{
use crate::vm::representations::{ClarityName, SymbolicExpression, depth_traverse};
use crate::vm::types::signatures::{FunctionSignature, TypeSignatureExt as _};
use crate::vm::types::{
- FixedFunction, FunctionArg, FunctionType, PrincipalData, QualifiedContractIdentifier,
- TypeSignature, Value, parse_name_type_pairs,
+ FixedFunction, FunctionArg, FunctionType, PrincipalData, TypeSignature, Value,
+ parse_name_type_pairs,
};
use crate::vm::variables::NativeVariables;
@@ -98,15 +98,6 @@ impl CostTracker for TypeChecker<'_, '_> {
fn reset_memory(&mut self) {
self.cost_track.reset_memory()
}
- fn short_circuit_contract_call(
- &mut self,
- contract: &QualifiedContractIdentifier,
- function: &ClarityName,
- input: &[u64],
- ) -> std::result::Result {
- self.cost_track
- .short_circuit_contract_call(contract, function, input)
- }
}
impl TypeChecker<'_, '_> {
diff --git a/clarity/src/vm/analysis/type_checker/v2_1/mod.rs b/clarity/src/vm/analysis/type_checker/v2_1/mod.rs
index 517c30b03af..9a07afd21dd 100644
--- a/clarity/src/vm/analysis/type_checker/v2_1/mod.rs
+++ b/clarity/src/vm/analysis/type_checker/v2_1/mod.rs
@@ -111,15 +111,6 @@ impl CostTracker for TypeChecker<'_, '_> {
fn reset_memory(&mut self) {
self.cost_track.reset_memory()
}
- fn short_circuit_contract_call(
- &mut self,
- contract: &QualifiedContractIdentifier,
- function: &ClarityName,
- input: &[u64],
- ) -> std::result::Result {
- self.cost_track
- .short_circuit_contract_call(contract, function, input)
- }
}
impl TypeChecker<'_, '_> {
diff --git a/clarity/src/vm/analysis/types.rs b/clarity/src/vm/analysis/types.rs
index 1821c3eb81a..e8b71a298de 100644
--- a/clarity/src/vm/analysis/types.rs
+++ b/clarity/src/vm/analysis/types.rs
@@ -56,7 +56,6 @@ pub struct ContractAnalysis {
pub defined_traits: BTreeMap>,
pub implemented_traits: BTreeSet,
pub contract_interface: Option,
- pub is_cost_contract_eligible: bool,
pub epoch: StacksEpochId,
pub clarity_version: ClarityVersion,
#[serde(skip)]
@@ -91,7 +90,6 @@ impl ContractAnalysis {
fungible_tokens: BTreeSet::new(),
non_fungible_tokens: BTreeMap::new(),
cost_track: Some(cost_track),
- is_cost_contract_eligible: false,
epoch,
clarity_version,
}
diff --git a/clarity/src/vm/ast/mod.rs b/clarity/src/vm/ast/mod.rs
index 561df22e8ad..a43ae4aea49 100644
--- a/clarity/src/vm/ast/mod.rs
+++ b/clarity/src/vm/ast/mod.rs
@@ -269,9 +269,7 @@ mod test {
use crate::vm::costs::{LimitedCostTracker, *};
use crate::vm::representations::depth_traverse;
use crate::vm::types::QualifiedContractIdentifier;
- use crate::vm::{
- ClarityCostFunction, ClarityName, ClarityVersion, max_call_stack_depth_for_epoch,
- };
+ use crate::vm::{ClarityCostFunction, ClarityVersion, max_call_stack_depth_for_epoch};
#[derive(PartialEq, Debug)]
struct UnitTestTracker {
@@ -309,14 +307,6 @@ mod test {
Ok(())
}
fn reset_memory(&mut self) {}
- fn short_circuit_contract_call(
- &mut self,
- _contract: &QualifiedContractIdentifier,
- _function: &ClarityName,
- _input: &[u64],
- ) -> Result {
- Ok(false)
- }
}
#[test]
diff --git a/clarity/src/vm/contexts.rs b/clarity/src/vm/contexts.rs
index b34eb7a3aef..1419b1e031f 100644
--- a/clarity/src/vm/contexts.rs
+++ b/clarity/src/vm/contexts.rs
@@ -16,7 +16,6 @@
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
-use std::mem::replace;
use std::time::{Duration, Instant};
use clarity_types::representations::ClarityName;
@@ -1114,16 +1113,6 @@ impl CostTracker for ExecutionState<'_, '_, '_> {
fn reset_memory(&mut self) {
self.global_context.cost_track.reset_memory()
}
- fn short_circuit_contract_call(
- &mut self,
- contract: &QualifiedContractIdentifier,
- function: &ClarityName,
- input: &[u64],
- ) -> std::result::Result {
- self.global_context
- .cost_track
- .short_circuit_contract_call(contract, function, input)
- }
}
impl CostTracker for GlobalContext<'_, '_> {
@@ -1147,35 +1136,9 @@ impl CostTracker for GlobalContext<'_, '_> {
fn reset_memory(&mut self) {
self.cost_track.reset_memory()
}
- fn short_circuit_contract_call(
- &mut self,
- contract: &QualifiedContractIdentifier,
- function: &ClarityName,
- input: &[u64],
- ) -> std::result::Result {
- self.cost_track
- .short_circuit_contract_call(contract, function, input)
- }
}
impl<'a, 'b, 'hooks> ExecutionState<'a, 'b, 'hooks> {
- /// Used only for contract-call! cost short-circuiting. Once the short-circuited cost
- /// has been evaluated and assessed, the contract-call! itself is executed "for free".
- pub fn run_free(&mut self, invoke_ctx: &InvocationContext, to_run: F) -> A
- where
- F: FnOnce(&mut ExecutionState, &InvocationContext) -> A,
- {
- let original_tracker = replace(
- &mut self.global_context.cost_track,
- LimitedCostTracker::new_free(),
- );
- // note: it is important that this method not return until original_tracker has been
- // restored. DO NOT use the try syntax (?).
- let result = to_run(self, invoke_ctx);
- self.global_context.cost_track = original_tracker;
- result
- }
-
pub fn eval_read_only(
&mut self,
invoke_ctx: &InvocationContext,
diff --git a/clarity/src/vm/costs/mod.rs b/clarity/src/vm/costs/mod.rs
index 9e14915e88c..a5dc7a2426e 100644
--- a/clarity/src/vm/costs/mod.rs
+++ b/clarity/src/vm/costs/mod.rs
@@ -23,7 +23,6 @@ use costs_2_testnet::Costs2Testnet;
use costs_3::Costs3;
use costs_4::Costs4;
use costs_5::Costs5;
-use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use stacks_common::types::StacksEpochId;
@@ -38,12 +37,8 @@ use crate::vm::database::ClarityDatabase;
use crate::vm::database::clarity_store::NullBackingStore;
use crate::vm::errors::VmExecutionError;
use crate::vm::types::Value::UInt;
-use crate::vm::types::signatures::FunctionType::Fixed;
-use crate::vm::types::signatures::TupleTypeSignature;
-use crate::vm::types::{
- FunctionType, PrincipalData, QualifiedContractIdentifier, TupleData, TypeSignature,
-};
-use crate::vm::{CallStack, ClarityName, LocalContext, SymbolicExpression, Value};
+use crate::vm::types::{PrincipalData, QualifiedContractIdentifier, TypeSignature};
+use crate::vm::{CallStack, LocalContext, SymbolicExpression, Value};
pub mod constants;
pub mod cost_functions;
#[allow(unused_variables)]
@@ -69,37 +64,6 @@ pub const COSTS_3_NAME: &str = "costs-3";
pub const COSTS_4_NAME: &str = "costs-4";
pub const COSTS_5_NAME: &str = "costs-5";
-lazy_static! {
- static ref COST_TUPLE_TYPE_SIGNATURE: TypeSignature = {
- #[allow(clippy::expect_used)]
- TypeSignature::TupleType(
- TupleTypeSignature::try_from(vec![
- (
- ClarityName::from_literal("runtime"),
- TypeSignature::UIntType,
- ),
- (
- ClarityName::from_literal("write_length"),
- TypeSignature::UIntType,
- ),
- (
- ClarityName::from_literal("write_count"),
- TypeSignature::UIntType,
- ),
- (
- ClarityName::from_literal("read_count"),
- TypeSignature::UIntType,
- ),
- (
- ClarityName::from_literal("read_length"),
- TypeSignature::UIntType,
- ),
- ])
- .expect("BUG: failed to construct type signature for cost tuple"),
- )
- };
-}
-
pub fn runtime_cost, C: CostTracker>(
cost_function: ClarityCostFunction,
tracker: &mut C,
@@ -156,15 +120,6 @@ pub trait CostTracker {
fn add_memory(&mut self, memory: u64) -> Result<(), CostErrors>;
fn drop_memory(&mut self, memory: u64) -> Result<(), CostErrors>;
fn reset_memory(&mut self);
- /// Check if the given contract-call should be short-circuited.
- /// If so: this charges the cost to the CostTracker, and return true
- /// If not: return false
- fn short_circuit_contract_call(
- &mut self,
- contract: &QualifiedContractIdentifier,
- function: &ClarityName,
- input: &[u64],
- ) -> Result;
}
// Don't track!
@@ -186,14 +141,6 @@ impl CostTracker for () {
Ok(())
}
fn reset_memory(&mut self) {}
- fn short_circuit_contract_call(
- &mut self,
- _contract: &QualifiedContractIdentifier,
- _function: &ClarityName,
- _input: &[u64],
- ) -> Result {
- Ok(false)
- }
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
@@ -298,64 +245,11 @@ impl ClarityCostFunctionReference {
}
}
-#[derive(Debug, Clone)]
-pub struct CostStateSummary {
- pub contract_call_circuits:
- HashMap<(QualifiedContractIdentifier, ClarityName), ClarityCostFunctionReference>,
- pub cost_function_references: HashMap,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-struct SerializedCostStateSummary {
- contract_call_circuits: Vec<(
- (QualifiedContractIdentifier, ClarityName),
- ClarityCostFunctionReference,
- )>,
- cost_function_references: Vec<(ClarityCostFunction, ClarityCostFunctionReference)>,
-}
-
-impl From for SerializedCostStateSummary {
- fn from(other: CostStateSummary) -> SerializedCostStateSummary {
- let CostStateSummary {
- contract_call_circuits,
- cost_function_references,
- } = other;
- SerializedCostStateSummary {
- contract_call_circuits: contract_call_circuits.into_iter().collect(),
- cost_function_references: cost_function_references.into_iter().collect(),
- }
- }
-}
-
-impl From for CostStateSummary {
- fn from(other: SerializedCostStateSummary) -> CostStateSummary {
- let SerializedCostStateSummary {
- contract_call_circuits,
- cost_function_references,
- } = other;
- CostStateSummary {
- contract_call_circuits: contract_call_circuits.into_iter().collect(),
- cost_function_references: cost_function_references.into_iter().collect(),
- }
- }
-}
-
-impl CostStateSummary {
- pub fn empty() -> CostStateSummary {
- CostStateSummary {
- contract_call_circuits: HashMap::new(),
- cost_function_references: HashMap::new(),
- }
- }
-}
-
#[derive(Clone)]
/// This struct holds all of the data required for non-free LimitedCostTracker instances
pub struct TrackerData {
cost_function_references: HashMap<&'static ClarityCostFunction, ClarityCostFunctionEvaluator>,
cost_contracts: HashMap,
- contract_call_circuits:
- HashMap<(QualifiedContractIdentifier, ClarityName), ClarityCostFunctionReference>,
total: ExecutionCost,
limit: ExecutionCost,
memory: u64,
@@ -377,17 +271,6 @@ pub enum LimitedCostTracker {
#[cfg(any(test, feature = "testing"))]
impl LimitedCostTracker {
- pub fn contract_call_circuits(
- &self,
- ) -> HashMap<(QualifiedContractIdentifier, ClarityName), ClarityCostFunctionReference> {
- match self {
- Self::Free => panic!("Cannot get contract call circuits on free tracker"),
- Self::Limited(TrackerData {
- contract_call_circuits,
- ..
- }) => contract_call_circuits.clone(),
- }
- }
pub fn cost_function_references(
&self,
) -> HashMap<&'static ClarityCostFunction, ClarityCostFunctionEvaluator> {
@@ -437,360 +320,6 @@ impl PartialEq for LimitedCostTracker {
}
}
-fn load_state_summary(
- mainnet: bool,
- clarity_db: &mut ClarityDatabase,
-) -> Result {
- let cost_voting_contract = boot_code_id("cost-voting", mainnet);
-
- let clarity_epoch = clarity_db
- .get_clarity_epoch_version()
- .map_err(|e| CostErrors::CostComputationFailed(e.to_string()))?;
- let last_processed_at = match clarity_db.get_value(
- "vm-costs::last-processed-at-height",
- &TypeSignature::UIntType,
- &clarity_epoch,
- ) {
- Ok(Some(v)) => u32::try_from(
- v.value
- .expect_u128()
- .map_err(|_| CostErrors::InterpreterFailure)?,
- )
- .map_err(|_| CostErrors::InterpreterFailure)?,
- Ok(None) => return Ok(CostStateSummary::empty()),
- Err(e) => return Err(CostErrors::CostComputationFailed(e.to_string())),
- };
-
- let metadata_result = clarity_db
- .fetch_metadata_manual::(
- last_processed_at,
- &cost_voting_contract,
- "::state_summary",
- )
- .map_err(|e| CostErrors::CostComputationFailed(e.to_string()))?;
- let serialized: SerializedCostStateSummary = match metadata_result {
- Some(serialized) => {
- serde_json::from_str(&serialized).map_err(|_| CostErrors::InterpreterFailure)?
- }
- None => return Ok(CostStateSummary::empty()),
- };
- Ok(CostStateSummary::from(serialized))
-}
-
-fn store_state_summary(
- mainnet: bool,
- clarity_db: &mut ClarityDatabase,
- to_store: &CostStateSummary,
-) -> Result<(), CostErrors> {
- let block_height = clarity_db.get_current_block_height();
- let cost_voting_contract = boot_code_id("cost-voting", mainnet);
- let epoch = clarity_db
- .get_clarity_epoch_version()
- .map_err(|e| CostErrors::CostComputationFailed(e.to_string()))?;
- clarity_db
- .put_value(
- "vm-costs::last-processed-at-height",
- Value::UInt(block_height as u128),
- &epoch,
- )
- .map_err(|_e| CostErrors::CostContractLoadFailure)?;
- let serialized_summary =
- serde_json::to_string(&SerializedCostStateSummary::from(to_store.clone()))
- .map_err(|_| CostErrors::InterpreterFailure)?;
- clarity_db
- .set_metadata(
- &cost_voting_contract,
- "::state_summary",
- &serialized_summary,
- )
- .map_err(|e| CostErrors::Expect(e.to_string()))?;
-
- Ok(())
-}
-
-///
-/// This method loads a cost state summary structure from the currently open stacks chain tip
-/// In doing so, it reads from the cost-voting contract to find any newly confirmed proposals,
-/// checks those proposals for validity, and then applies those changes to the cached set
-/// of cost functions.
-///
-/// `apply_updates` - tells this function to look for any changes in the cost voting contract
-/// which would need to be applied. if `false`, just load the last computed cost state in this
-/// fork.
-///
-fn load_cost_functions(
- mainnet: bool,
- clarity_db: &mut ClarityDatabase,
- apply_updates: bool,
-) -> Result {
- let clarity_epoch = clarity_db
- .get_clarity_epoch_version()
- .map_err(|e| CostErrors::CostComputationFailed(e.to_string()))?;
- let last_processed_count = clarity_db
- .get_value(
- "vm-costs::last_processed_count",
- &TypeSignature::UIntType,
- &clarity_epoch,
- )
- .map_err(|_e| CostErrors::CostContractLoadFailure)?
- .map(|result| result.value)
- .unwrap_or(Value::UInt(0))
- .expect_u128()
- .map_err(|_| CostErrors::InterpreterFailure)?;
- let cost_voting_contract = boot_code_id("cost-voting", mainnet);
- let confirmed_proposals_count = clarity_db
- .lookup_variable_unknown_descriptor(
- &cost_voting_contract,
- "confirmed-proposal-count",
- &clarity_epoch,
- )
- .map_err(|e| CostErrors::CostComputationFailed(e.to_string()))?
- .expect_u128()
- .map_err(|_| CostErrors::InterpreterFailure)?;
- debug!("Check cost voting contract";
- "confirmed_proposal_count" => confirmed_proposals_count,
- "last_processed_count" => last_processed_count);
-
- // we need to process any confirmed proposals in the range [fetch-start, fetch-end)
- let (fetch_start, fetch_end) = (last_processed_count, confirmed_proposals_count);
- let mut state_summary = load_state_summary(mainnet, clarity_db)?;
- if !apply_updates {
- return Ok(state_summary);
- }
-
- for confirmed_proposal in fetch_start..fetch_end {
- // fetch the proposal data
- let entry = clarity_db
- .fetch_entry_unknown_descriptor(
- &cost_voting_contract,
- "confirmed-proposals",
- &Value::from(
- TupleData::from_data(vec![(
- ClarityName::from_literal("confirmed-id"),
- Value::UInt(confirmed_proposal),
- )])
- .map_err(|_| {
- CostErrors::Expect("BUG: failed to construct simple tuple".into())
- })?,
- ),
- &clarity_epoch,
- )
- .map_err(|_| CostErrors::Expect("BUG: Failed querying confirmed-proposals".into()))?
- .expect_optional()
- .map_err(|_| CostErrors::InterpreterFailure)?
- .ok_or_else(|| {
- CostErrors::Expect("BUG: confirmed-proposal-count exceeds stored proposals".into())
- })?
- .expect_tuple()
- .map_err(|_| CostErrors::InterpreterFailure)?;
- let target_contract = match entry
- .get("function-contract")
- .map_err(|_| CostErrors::Expect("BUG: malformed cost proposal tuple".into()))?
- .clone()
- .expect_principal()
- .map_err(|_| CostErrors::InterpreterFailure)?
- {
- PrincipalData::Contract(contract_id) => contract_id,
- _ => {
- warn!("Confirmed cost proposal invalid: function-contract is not a contract principal";
- "confirmed_proposal_id" => confirmed_proposal);
- continue;
- }
- };
- let target_function = match ClarityName::try_from(
- entry
- .get("function-name")
- .map_err(|_| CostErrors::Expect("BUG: malformed cost proposal tuple".into()))?
- .clone()
- .expect_ascii()
- .map_err(|_| CostErrors::InterpreterFailure)?,
- ) {
- Ok(x) => x,
- Err(_) => {
- warn!("Confirmed cost proposal invalid: function-name is not a valid function name";
- "confirmed_proposal_id" => confirmed_proposal);
- continue;
- }
- };
- let cost_contract = match entry
- .get("cost-function-contract")
- .map_err(|_| CostErrors::Expect("BUG: malformed cost proposal tuple".into()))?
- .clone()
- .expect_principal()
- .map_err(|_| CostErrors::InterpreterFailure)?
- {
- PrincipalData::Contract(contract_id) => contract_id,
- _ => {
- warn!("Confirmed cost proposal invalid: cost-function-contract is not a contract principal";
- "confirmed_proposal_id" => confirmed_proposal);
- continue;
- }
- };
-
- let cost_function = match ClarityName::try_from(
- entry
- .get_owned("cost-function-name")
- .map_err(|_| CostErrors::Expect("BUG: malformed cost proposal tuple".into()))?
- .expect_ascii()
- .map_err(|_| CostErrors::InterpreterFailure)?,
- ) {
- Ok(x) => x,
- Err(_) => {
- warn!("Confirmed cost proposal invalid: cost-function-name is not a valid function name";
- "confirmed_proposal_id" => confirmed_proposal);
- continue;
- }
- };
-
- // Here is where we perform the required validity checks for a confirmed proposal:
- // * Replaced contract-calls _must_ be `define-read-only` _or_ refer to one of the boot code
- // cost functions
- // * cost-function contracts must be arithmetic only
-
- // make sure the contract is "cost contract eligible" via the
- // arithmetic-checking analysis pass
- let (cost_func_ref, cost_func_type) = match clarity_db
- .load_contract_analysis(&cost_contract)
- .map_err(|e| CostErrors::CostComputationFailed(e.to_string()))?
- {
- Some(c) => {
- if !c.is_cost_contract_eligible {
- warn!("Confirmed cost proposal invalid: cost-function-contract uses non-arithmetic or otherwise illegal operations";
- "confirmed_proposal_id" => confirmed_proposal,
- "contract_name" => %cost_contract,
- );
- continue;
- }
-
- if let Some(FunctionType::Fixed(cost_function_type)) = c
- .read_only_function_types
- .get(&cost_function)
- .or_else(|| c.private_function_types.get(&cost_function))
- {
- if !cost_function_type.returns.eq(&COST_TUPLE_TYPE_SIGNATURE) {
- warn!("Confirmed cost proposal invalid: cost-function-name does not return a cost tuple";
- "confirmed_proposal_id" => confirmed_proposal,
- "contract_name" => %cost_contract,
- "function_name" => %cost_function,
- "return_type" => %cost_function_type.returns,
- );
- continue;
- }
- if !cost_function_type.args.len() == 1
- || cost_function_type.args[0].signature != TypeSignature::UIntType
- {
- warn!("Confirmed cost proposal invalid: cost-function-name args should be length-1 and only uint";
- "confirmed_proposal_id" => confirmed_proposal,
- "contract_name" => %cost_contract,
- "function_name" => %cost_function,
- );
- continue;
- }
- (
- ClarityCostFunctionReference {
- contract_id: cost_contract,
- function_name: cost_function.to_string(),
- },
- cost_function_type.clone(),
- )
- } else {
- warn!("Confirmed cost proposal invalid: cost-function-name not defined";
- "confirmed_proposal_id" => confirmed_proposal,
- "contract_name" => %cost_contract,
- "function_name" => %cost_function,
- );
- continue;
- }
- }
- None => {
- warn!("Confirmed cost proposal invalid: cost-function-contract is not a published contract";
- "confirmed_proposal_id" => confirmed_proposal,
- "contract_name" => %cost_contract,
- );
- continue;
- }
- };
-
- if target_contract == boot_code_id("costs", mainnet) {
- // refering to one of the boot code cost functions
- let target = match ClarityCostFunction::lookup_by_name(&target_function) {
- Some(ClarityCostFunction::Unimplemented) => {
- warn!("Attempted vote on unimplemented cost function";
- "confirmed_proposal_id" => confirmed_proposal,
- "cost_function" => %target_function);
- continue;
- }
- Some(cost_func) => cost_func,
- None => {
- warn!("Confirmed cost proposal invalid: function-name does not reference a Clarity cost function";
- "confirmed_proposal_id" => confirmed_proposal,
- "cost_function" => %target_function);
- continue;
- }
- };
- state_summary
- .cost_function_references
- .insert(target, cost_func_ref);
- } else {
- // referring to a user-defined function
- match clarity_db
- .load_contract_analysis(&target_contract)
- .map_err(|e| CostErrors::CostComputationFailed(e.to_string()))?
- {
- Some(c) => {
- if let Some(Fixed(tf)) = c.read_only_function_types.get(&target_function) {
- if cost_func_type.args.len() != tf.args.len() {
- warn!("Confirmed cost proposal invalid: cost-function contains the wrong number of arguments";
- "confirmed_proposal_id" => confirmed_proposal,
- "target_contract_name" => %target_contract,
- "target_function_name" => %target_function,
- );
- continue;
- }
- for arg in &cost_func_type.args {
- if arg.signature != TypeSignature::UIntType {
- warn!("Confirmed cost proposal invalid: contains non uint argument";
- "confirmed_proposal_id" => confirmed_proposal,
- );
- continue;
- }
- }
- } else {
- warn!("Confirmed cost proposal invalid: function-name not defined or is not read-only";
- "confirmed_proposal_id" => confirmed_proposal,
- "target_contract_name" => %target_contract,
- "target_function_name" => %target_function,
- );
- continue;
- }
- }
- None => {
- warn!("Confirmed cost proposal invalid: contract-name not a published contract";
- "confirmed_proposal_id" => confirmed_proposal,
- "target_contract_name" => %target_contract,
- );
- continue;
- }
- }
- state_summary
- .contract_call_circuits
- .insert((target_contract, target_function), cost_func_ref);
- }
- }
- if confirmed_proposals_count > last_processed_count {
- store_state_summary(mainnet, clarity_db, &state_summary)?;
- clarity_db
- .put_value(
- "vm-costs::last_processed_count",
- Value::UInt(confirmed_proposals_count),
- &clarity_epoch,
- )
- .map_err(|_e| CostErrors::CostContractLoadFailure)?;
- }
-
- Ok(state_summary)
-}
-
impl LimitedCostTracker {
pub fn new(
mainnet: bool,
@@ -802,7 +331,6 @@ impl LimitedCostTracker {
let mut cost_tracker = TrackerData {
cost_function_references: HashMap::new(),
cost_contracts: HashMap::new(),
- contract_call_circuits: HashMap::new(),
limit,
memory_limit: CLARITY_MEMORY_LIMIT,
total: ExecutionCost::ZERO,
@@ -812,7 +340,7 @@ impl LimitedCostTracker {
chain_id,
};
assert!(clarity_db.is_stack_empty());
- cost_tracker.load_costs(clarity_db, true)?;
+ cost_tracker.load_costs(clarity_db)?;
Ok(Self::Limited(cost_tracker))
}
@@ -826,7 +354,6 @@ impl LimitedCostTracker {
let mut cost_tracker = TrackerData {
cost_function_references: HashMap::new(),
cost_contracts: HashMap::new(),
- contract_call_circuits: HashMap::new(),
limit,
memory_limit: CLARITY_MEMORY_LIMIT,
total: ExecutionCost::ZERO,
@@ -835,7 +362,7 @@ impl LimitedCostTracker {
mainnet,
chain_id,
};
- cost_tracker.load_costs(clarity_db, false)?;
+ cost_tracker.load_costs(clarity_db)?;
Ok(Self::Limited(cost_tracker))
}
@@ -913,7 +440,6 @@ impl LimitedCostTracker {
let cost_tracker = TrackerData {
cost_function_references: cost_functions,
cost_contracts: HashMap::new(),
- contract_call_circuits: HashMap::new(),
limit,
memory_limit: CLARITY_MEMORY_LIMIT,
total: ExecutionCost::ZERO,
@@ -928,16 +454,11 @@ impl LimitedCostTracker {
}
impl TrackerData {
- // TODO: add tests from mutation testing results #4831
- #[cfg_attr(test, mutants::skip)]
- /// `apply_updates` - tells this function to look for any changes in the cost voting contract
- /// which would need to be applied. if `false`, just load the last computed cost state in this
- /// fork.
- fn load_costs(
- &mut self,
- clarity_db: &mut ClarityDatabase,
- apply_updates: bool,
- ) -> Result<(), CostErrors> {
+ /// Load the default cost functions for this tracker's epoch.
+ ///
+ /// Every Clarity cost function is evaluated against the boot cost contract
+ /// for the current epoch (`costs`, `costs-2`, `costs-3`, `costs-4`, or `costs-5`).
+ fn load_costs(&mut self, clarity_db: &mut ClarityDatabase) -> Result<(), CostErrors> {
clarity_db.begin();
let epoch_id = clarity_db
.get_clarity_epoch_version()
@@ -953,97 +474,38 @@ impl TrackerData {
))
})?;
- // TODO(cost-voting): Remove after epoch 4.0 activation (when cost-voting deactivates), and
- // use `CostStateSummary::empty()` directly instead.
- let state_summary = if epoch_id.supports_cost_voting_contract() {
- // If cost-voting is active, load and apply the current cost function configuration from
- // the chain tip, applying any changes that have been voted on but not yet applied.
- load_cost_functions(self.mainnet, clarity_db, apply_updates).map_err(|e| {
- let result = clarity_db
+ let boot_cost_contract = match clarity_db.get_contract(&boot_costs_id) {
+ Ok(contract) => contract,
+ Err(e) => {
+ error!("Failed to load intended Clarity cost contract";
+ "contract" => %boot_costs_id,
+ "error" => ?e);
+ clarity_db
.roll_back()
- .map_err(|e| CostErrors::Expect(e.to_string()));
- match result {
- Ok(_) => e,
- Err(rollback_err) => rollback_err,
- }
- })?
- } else {
- // Cost-voting is retired at Epoch 4.0: every cost function uses boot defaults.
- CostStateSummary::empty()
- };
-
- let CostStateSummary {
- contract_call_circuits,
- mut cost_function_references,
- } = state_summary;
-
- self.contract_call_circuits = contract_call_circuits;
-
- let iter = ClarityCostFunction::ALL.iter();
- let iter_len = iter.len();
- let mut cost_contracts = HashMap::with_capacity(iter_len);
- let mut m = HashMap::with_capacity(iter_len);
-
- for f in iter {
- let cost_function_ref = cost_function_references.remove(f).unwrap_or_else(|| {
- ClarityCostFunctionReference::new(boot_costs_id.clone(), f.get_name())
- });
- if !cost_contracts.contains_key(&cost_function_ref.contract_id) {
- let contract = match clarity_db.get_contract(&cost_function_ref.contract_id) {
- Ok(contract) => contract,
- Err(e) => {
- error!("Failed to load intended Clarity cost contract";
- "contract" => %cost_function_ref.contract_id,
- "error" => ?e);
- clarity_db
- .roll_back()
- .map_err(|e| CostErrors::Expect(e.to_string()))?;
- return Err(CostErrors::CostContractLoadFailure);
- }
- };
- cost_contracts.insert(cost_function_ref.contract_id.clone(), contract);
+ .map_err(|e| CostErrors::Expect(e.to_string()))?;
+ return Err(CostErrors::CostContractLoadFailure);
}
+ };
- if cost_function_ref.contract_id == boot_costs_id {
- m.insert(
- f,
- ClarityCostFunctionEvaluator::Default(cost_function_ref, f.clone(), v),
- );
- } else {
- m.insert(f, ClarityCostFunctionEvaluator::Clarity(cost_function_ref));
- }
+ let mut m = HashMap::with_capacity(ClarityCostFunction::ALL.len());
+ for f in ClarityCostFunction::ALL.iter() {
+ let cost_function_ref =
+ ClarityCostFunctionReference::new(boot_costs_id.clone(), f.get_name());
+ m.insert(
+ f,
+ ClarityCostFunctionEvaluator::Default(cost_function_ref, f.clone(), v),
+ );
}
- for (_, circuit_target) in self.contract_call_circuits.iter() {
- if !cost_contracts.contains_key(&circuit_target.contract_id) {
- let contract = match clarity_db.get_contract(&circuit_target.contract_id) {
- Ok(contract) => contract,
- Err(e) => {
- error!("Failed to load intended Clarity cost contract";
- "contract" => %boot_costs_id.to_string(),
- "error" => %format!("{:?}", e));
- clarity_db
- .roll_back()
- .map_err(|e| CostErrors::Expect(e.to_string()))?;
- return Err(CostErrors::CostContractLoadFailure);
- }
- };
- cost_contracts.insert(circuit_target.contract_id.clone(), contract);
- }
- }
+ let mut cost_contracts = HashMap::with_capacity(1);
+ cost_contracts.insert(boot_costs_id, boot_cost_contract);
self.cost_function_references = m;
self.cost_contracts = cost_contracts;
- if apply_updates {
- clarity_db
- .commit()
- .map_err(|e| CostErrors::Expect(e.to_string()))?;
- } else {
- clarity_db
- .roll_back()
- .map_err(|e| CostErrors::Expect(e.to_string()))?;
- }
+ clarity_db
+ .commit()
+ .map_err(|e| CostErrors::Expect(e.to_string()))?;
Ok(())
}
@@ -1285,29 +747,6 @@ impl CostTracker for LimitedCostTracker {
}
}
}
- fn short_circuit_contract_call(
- &mut self,
- contract: &QualifiedContractIdentifier,
- function: &ClarityName,
- input: &[u64],
- ) -> Result {
- match self {
- Self::Free => {
- // if we're already free, no need to worry about short circuiting contract-calls
- Ok(false)
- }
- Self::Limited(data) => {
- // grr, if HashMap::get didn't require Borrow, we wouldn't need this cloning.
- let lookup_key = (contract.clone(), function.clone());
- if let Some(cost_function) = data.contract_call_circuits.get(&lookup_key).cloned() {
- compute_cost(data, cost_function, input, data.epoch)?;
- Ok(true)
- } else {
- Ok(false)
- }
- }
- }
- }
}
impl CostTracker for &mut LimitedCostTracker {
@@ -1330,14 +769,6 @@ impl CostTracker for &mut LimitedCostTracker {
fn reset_memory(&mut self) {
LimitedCostTracker::reset_memory(self)
}
- fn short_circuit_contract_call(
- &mut self,
- contract: &QualifiedContractIdentifier,
- function: &ClarityName,
- input: &[u64],
- ) -> Result {
- LimitedCostTracker::short_circuit_contract_call(self, contract, function, input)
- }
}
// ONLY WORKS IF INPUT IS u64
diff --git a/clarity/src/vm/functions/database.rs b/clarity/src/vm/functions/database.rs
index e3adb9b2f45..380a446f7ab 100644
--- a/clarity/src/vm/functions/database.rs
+++ b/clarity/src/vm/functions/database.rs
@@ -81,10 +81,8 @@ pub fn special_contract_call(
let rest_args_slice = &args[2..];
let rest_args_len = rest_args_slice.len();
let mut rest_args = Vec::with_capacity(rest_args_len);
- let mut rest_args_sizes = Vec::with_capacity(rest_args_len);
for arg in rest_args_slice.iter() {
let evaluated_arg = eval(arg, exec_state, invoke_ctx, context)?;
- rest_args_sizes.push(evaluated_arg.as_ref().size()?.into());
rest_args.push(SymbolicExpression::atom_value(
evaluated_arg.clone_with_cost(exec_state)?,
));
@@ -238,29 +236,13 @@ pub fn special_contract_call(
.into();
let nested_ctx = invoke_ctx.with_caller(contract_principal);
- let result = if exec_state.short_circuit_contract_call(
+ let result = exec_state.execute_contract(
+ &nested_ctx,
&contract_identifier,
function_name,
- &rest_args_sizes,
- )? {
- exec_state.run_free(&nested_ctx, |free_exec_state, nested_ctx| {
- free_exec_state.execute_contract(
- nested_ctx,
- &contract_identifier,
- function_name,
- &rest_args,
- false,
- )
- })
- } else {
- exec_state.execute_contract(
- &nested_ctx,
- &contract_identifier,
- function_name,
- &rest_args,
- false,
- )
- }?;
+ &rest_args,
+ false,
+ )?;
// sanitize contract-call outputs in epochs >= 2.4
let result_type = TypeSignature::type_of(&result)?;
diff --git a/stacks-common/src/types/mod.rs b/stacks-common/src/types/mod.rs
index f86afc73e3f..7d4fa5ebf01 100644
--- a/stacks-common/src/types/mod.rs
+++ b/stacks-common/src/types/mod.rs
@@ -598,12 +598,6 @@ impl StacksEpochId {
}
}
- /// Whether or not this epoch supports the cost-voting contract (SIP-006), which is
- /// disabled from Epoch 4.0 (SIP-044).
- pub fn supports_cost_voting_contract(&self) -> bool {
- self < &StacksEpochId::Epoch40
- }
-
/// Returns true for epochs which use Nakamoto blocks. These blocks use a
/// different header format than the previous Stacks blocks, which among
/// other changes includes a Stacks-specific timestamp.
diff --git a/stacks-common/src/types/tests.rs b/stacks-common/src/types/tests.rs
index 89bff743494..32bbaa9b4ce 100644
--- a/stacks-common/src/types/tests.rs
+++ b/stacks-common/src/types/tests.rs
@@ -23,25 +23,6 @@ use super::{
};
use crate::types::StacksEpochRangeTestExt;
-#[test]
-fn test_epoch40_cost_voting_contract_support_gate() {
- // Up to (excluding) Epoch 4.0 = enabled.
- for epoch in (..StacksEpochId::Epoch40).iter() {
- assert!(
- epoch.supports_cost_voting_contract(),
- "cost-voting gate must remain active before Epoch 4.0 for {epoch}"
- );
- }
-
- // Epoch 4.0 and onward = disabled.
- for epoch in (StacksEpochId::Epoch40..).iter() {
- assert!(
- !epoch.supports_cost_voting_contract(),
- "cost-voting gate must be inactive from Epoch 4.0 onward for {epoch}"
- );
- }
-}
-
#[test]
fn test_epoch_range_ext_iter() {
// Alias to keep the below cleaner
diff --git a/stacks-node/src/tests/neon_integrations.rs b/stacks-node/src/tests/neon_integrations.rs
index 916e3a5b297..08d1d3a035b 100644
--- a/stacks-node/src/tests/neon_integrations.rs
+++ b/stacks-node/src/tests/neon_integrations.rs
@@ -4182,352 +4182,6 @@ fn block_replay_integration_test() {
channel.stop_chains_coordinator();
}
-#[test]
-#[ignore]
-fn cost_voting_integration() {
- if env::var("BITCOIND_TEST") != Ok("1".into()) {
- return;
- }
-
- // let's make `<` free...
- let cost_definer_src = "
- (define-read-only (cost-definition-le (size uint))
- {
- runtime: u0, write_length: u0, write_count: u0, read_count: u0, read_length: u0
- })
- ";
-
- // the contract that we'll test the costs of
- let caller_src = "
- (define-public (execute-2 (a uint))
- (ok (< a a)))
- ";
-
- let power_vote_src = "
- (define-public (propose-vote-confirm)
- (let
- ((proposal-id (unwrap-panic (contract-call? 'ST000000000000000000002AMW42H.cost-voting submit-proposal
- 'ST000000000000000000002AMW42H.costs \"cost_le\"
- .cost-definer \"cost-definition-le\")))
- (vote-amount (* u9000000000 u1000000)))
- (try! (contract-call? 'ST000000000000000000002AMW42H.cost-voting vote-proposal proposal-id vote-amount))
- (try! (contract-call? 'ST000000000000000000002AMW42H.cost-voting confirm-votes proposal-id))
- (ok proposal-id)))
- ";
-
- let spender_sk = StacksPrivateKey::random();
- let spender_addr = to_addr(&spender_sk);
- let spender_princ: PrincipalData = spender_addr.clone().into();
-
- let (mut conf, miner_account) = neon_integration_test_conf();
-
- conf.miner.microblock_attempt_time_ms = 1_000;
- conf.node.wait_time_for_microblocks = 0;
- conf.node.microblock_frequency = 1_000;
- conf.miner.first_attempt_time_ms = 2_000;
- conf.miner.subsequent_attempt_time_ms = 5_000;
- conf.burnchain.max_rbf = 10_000_000;
- conf.node.wait_time_for_blocks = 1_000;
-
- test_observer::spawn();
- test_observer::register_any(&mut conf);
-
- let spender_bal = 10_000_000_000 * u64::from(core::MICROSTACKS_PER_STACKS);
-
- conf.initial_balances.push(InitialBalance {
- address: spender_princ.clone(),
- amount: spender_bal,
- });
-
- let mut btcd_controller = BitcoinCoreController::from_stx_config(&conf);
- btcd_controller
- .start_bitcoind()
- .expect("Failed starting bitcoind");
-
- let burnchain_config = Burnchain::regtest(&conf.get_burn_db_path());
-
- let mut btc_regtest_controller = BitcoinRegtestController::with_burnchain(
- conf.clone(),
- None,
- Some(burnchain_config.clone()),
- None,
- );
- let http_origin = format!("http://{}", &conf.node.rpc_bind);
-
- btc_regtest_controller.bootstrap_chain(201);
-
- eprintln!("Chain bootstrapped...");
-
- let mut run_loop = neon::RunLoop::new(conf.clone());
- let blocks_processed = run_loop.get_blocks_processed_arc();
- let channel = run_loop.get_coordinator_channel().unwrap();
-
- thread::spawn(move || run_loop.start(Some(burnchain_config), 0));
-
- // give the run loop some time to start up!
- wait_for_runloop(&blocks_processed);
-
- // first block wakes up the run loop
- next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
-
- // first block will hold our VRF registration
- next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
-
- // second block will be the first mined Stacks block
- next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
-
- // let's query the miner's account nonce:
- let res = get_account(&http_origin, &miner_account);
- assert_eq!(res.balance, 0);
- assert_eq!(res.nonce, 1);
-
- // and our spender:
- let res = get_account(&http_origin, &spender_princ);
- assert_eq!(res.balance, spender_bal as u128);
- assert_eq!(res.nonce, 0);
-
- let transactions = vec![
- make_contract_publish(
- &spender_sk,
- 0,
- 1000,
- conf.burnchain.chain_id,
- "cost-definer",
- cost_definer_src,
- ),
- make_contract_publish(
- &spender_sk,
- 1,
- 1000,
- conf.burnchain.chain_id,
- "caller",
- caller_src,
- ),
- make_contract_publish(
- &spender_sk,
- 2,
- 1000,
- conf.burnchain.chain_id,
- "voter",
- power_vote_src,
- ),
- ];
-
- for tx in transactions.into_iter() {
- submit_tx(&http_origin, &tx);
- }
-
- next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
- next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
-
- let vote_tx = make_contract_call(
- &spender_sk,
- 3,
- 1000,
- conf.burnchain.chain_id,
- &spender_addr,
- "voter",
- "propose-vote-confirm",
- &[],
- );
-
- let call_le_tx = make_contract_call(
- &spender_sk,
- 4,
- 1000,
- conf.burnchain.chain_id,
- &spender_addr,
- "caller",
- "execute-2",
- &[Value::UInt(1)],
- );
-
- test_observer::clear();
- submit_tx(&http_origin, &vote_tx);
- submit_tx(&http_origin, &call_le_tx);
-
- // Mine blocks until both txs are confirmed (nonces 3 and 4)
- mine_blocks_until(&btc_regtest_controller, &blocks_processed, 3, 60, || {
- let res = get_account(&http_origin, &spender_princ);
- Ok(res.nonce >= 5)
- })
- .expect("vote and execute txs should have been mined");
-
- let blocks = test_observer::get_blocks();
- let mut tested = false;
- let mut exec_cost = ExecutionCost::ZERO;
- for block in blocks.iter() {
- let transactions = block.get("transactions").unwrap().as_array().unwrap();
- for tx in transactions.iter() {
- let raw_tx = tx.get("raw_tx").unwrap().as_str().unwrap();
- if raw_tx == "0x00" {
- continue;
- }
- let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap();
- let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap();
- if let TransactionPayload::ContractCall(contract_call) = parsed.payload {
- eprintln!("{}", contract_call.function_name.as_str());
- if contract_call.function_name.as_str() == "execute-2" {
- exec_cost =
- serde_json::from_value(tx.get("execution_cost").cloned().unwrap()).unwrap();
- } else if contract_call.function_name.as_str() == "propose-vote-confirm" {
- let raw_result = tx.get("raw_result").unwrap().as_str().unwrap();
- let parsed = Value::try_deserialize_hex_untyped(&raw_result[2..]).unwrap();
- assert_eq!(parsed.to_string(), "(ok u0)");
- tested = true;
- }
- }
- }
- }
- assert!(tested, "Should have found a contract call tx");
-
- // try to confirm the passed vote (this will fail)
- let confirm_proposal = make_contract_call(
- &spender_sk,
- 5,
- 1000,
- conf.burnchain.chain_id,
- &StacksAddress::from_string("ST000000000000000000002AMW42H").unwrap(),
- "cost-voting",
- "confirm-miners",
- &[Value::UInt(0)],
- );
-
- test_observer::clear();
- submit_tx(&http_origin, &confirm_proposal);
-
- // Mine blocks until early confirm-miners is confirmed (nonce 5)
- mine_blocks_until(&btc_regtest_controller, &blocks_processed, 3, 60, || {
- let res = get_account(&http_origin, &spender_princ);
- Ok(res.nonce >= 6)
- })
- .expect("early confirm-miners tx should have been mined");
-
- let blocks = test_observer::get_blocks();
- let mut tested = false;
- for block in blocks.iter() {
- let transactions = block.get("transactions").unwrap().as_array().unwrap();
- for tx in transactions.iter() {
- let raw_tx = tx.get("raw_tx").unwrap().as_str().unwrap();
- if raw_tx == "0x00" {
- continue;
- }
- let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap();
- let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap();
- if let TransactionPayload::ContractCall(contract_call) = parsed.payload {
- eprintln!("{}", contract_call.function_name.as_str());
- if contract_call.function_name.as_str() == "confirm-miners" {
- let raw_result = tx.get("raw_result").unwrap().as_str().unwrap();
- let parsed = Value::try_deserialize_hex_untyped(&raw_result[2..]).unwrap();
- assert_eq!(parsed.to_string(), "(err 13)");
- tested = true;
- }
- }
- }
- }
- assert!(tested, "Should have found a contract call tx");
-
- for _i in 0..58 {
- next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
- }
-
- // confirm the passed vote
- let confirm_proposal = make_contract_call(
- &spender_sk,
- 6,
- 1000,
- conf.burnchain.chain_id,
- &StacksAddress::from_string("ST000000000000000000002AMW42H").unwrap(),
- "cost-voting",
- "confirm-miners",
- &[Value::UInt(0)],
- );
-
- test_observer::clear();
- submit_tx(&http_origin, &confirm_proposal);
-
- // Mine blocks until confirm-miners after maturation is confirmed (nonce 6)
- mine_blocks_until(&btc_regtest_controller, &blocks_processed, 3, 60, || {
- let res = get_account(&http_origin, &spender_princ);
- Ok(res.nonce >= 7)
- })
- .expect("confirm-miners tx should have been mined");
-
- let blocks = test_observer::get_blocks();
- let mut tested = false;
- for block in blocks.iter() {
- let transactions = block.get("transactions").unwrap().as_array().unwrap();
- for tx in transactions.iter() {
- let raw_tx = tx.get("raw_tx").unwrap().as_str().unwrap();
- if raw_tx == "0x00" {
- continue;
- }
- let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap();
- let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap();
- if let TransactionPayload::ContractCall(contract_call) = parsed.payload {
- eprintln!("{}", contract_call.function_name.as_str());
- if contract_call.function_name.as_str() == "confirm-miners" {
- let raw_result = tx.get("raw_result").unwrap().as_str().unwrap();
- let parsed = Value::try_deserialize_hex_untyped(&raw_result[2..]).unwrap();
- assert_eq!(parsed.to_string(), "(ok true)");
- tested = true;
- }
- }
- }
- }
- assert!(tested, "Should have found a contract call tx");
-
- let call_le_tx = make_contract_call(
- &spender_sk,
- 7,
- 1000,
- conf.burnchain.chain_id,
- &spender_addr,
- "caller",
- "execute-2",
- &[Value::UInt(1)],
- );
-
- test_observer::clear();
- submit_tx(&http_origin, &call_le_tx);
-
- // Mine blocks until execute-2 with new cost is confirmed (nonce 7)
- mine_blocks_until(&btc_regtest_controller, &blocks_processed, 3, 60, || {
- let res = get_account(&http_origin, &spender_princ);
- Ok(res.nonce >= 8)
- })
- .expect("execute-2 tx should have been mined");
-
- let blocks = test_observer::get_blocks();
- let mut tested = false;
- let mut new_exec_cost = ExecutionCost::max_value();
- for block in blocks.iter() {
- let transactions = block.get("transactions").unwrap().as_array().unwrap();
- for tx in transactions.iter() {
- let raw_tx = tx.get("raw_tx").unwrap().as_str().unwrap();
- if raw_tx == "0x00" {
- continue;
- }
- let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap();
- let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap();
- if let TransactionPayload::ContractCall(contract_call) = parsed.payload {
- eprintln!("{}", contract_call.function_name.as_str());
- if contract_call.function_name.as_str() == "execute-2" {
- new_exec_cost =
- serde_json::from_value(tx.get("execution_cost").cloned().unwrap()).unwrap();
- tested = true;
- }
- }
- }
- }
- assert!(tested, "Should have found a contract call tx");
-
- assert!(exec_cost.exceeds(&new_exec_cost));
-
- test_observer::clear();
- channel.stop_chains_coordinator();
-}
-
#[test]
#[ignore]
fn mining_events_integration_test() {
diff --git a/stackslib/src/chainstate/stacks/boot/contract_tests.rs b/stackslib/src/chainstate/stacks/boot/contract_tests.rs
index 87f3b607018..c5c42552b95 100644
--- a/stackslib/src/chainstate/stacks/boot/contract_tests.rs
+++ b/stackslib/src/chainstate/stacks/boot/contract_tests.rs
@@ -15,16 +15,13 @@
use std::ops::Deref;
use clarity::util::get_epoch_time_secs;
-use clarity::vm::analysis::arithmetic_checker::ArithmeticOnlyChecker;
-use clarity::vm::analysis::mem_type_check;
use clarity::vm::clarity::TransactionConnection;
use clarity::vm::contexts::OwnedEnvironment;
use clarity::vm::database::*;
use clarity::vm::errors::{RuntimeCheckErrorKind, StaticCheckErrorKind, VmExecutionError};
use clarity::vm::test_util::{execute, symbols_from_values, TEST_BURN_STATE_DB, TEST_HEADER_DB};
use clarity::vm::types::{
- OptionalData, PrincipalData, QualifiedContractIdentifier, ResponseData, StandardPrincipalData,
- TupleData, Value,
+ PrincipalData, QualifiedContractIdentifier, StandardPrincipalData, TupleData, Value,
};
use clarity::vm::version::ClarityVersion;
use lazy_static::lazy_static;
@@ -38,10 +35,7 @@ use super::SIGNERS_MAX_LIST_SIZE;
use crate::burnchains::PoxConstants;
use crate::chainstate::burn::ConsensusHash;
use crate::chainstate::stacks::address::PoxAddress;
-use crate::chainstate::stacks::boot::{
- BOOT_CODE_COST_VOTING_TESTNET as BOOT_CODE_COST_VOTING, BOOT_CODE_POX_TESTNET,
- POX_2_TESTNET_CODE,
-};
+use crate::chainstate::stacks::boot::{BOOT_CODE_POX_TESTNET, POX_2_TESTNET_CODE};
use crate::chainstate::stacks::index::ClarityMarfTrieId;
use crate::chainstate::stacks::{C32_ADDRESS_VERSION_TESTNET_SINGLESIG, *};
use crate::clarity_vm::clarity::{
@@ -67,8 +61,6 @@ lazy_static! {
pub static ref POX_CONTRACT_TESTNET: QualifiedContractIdentifier = boot_code_id("pox", false);
pub static ref POX_2_CONTRACT_TESTNET: QualifiedContractIdentifier =
boot_code_id("pox-2", false);
- pub static ref COST_VOTING_CONTRACT_TESTNET: QualifiedContractIdentifier =
- boot_code_id("cost-voting", false);
pub static ref USER_KEYS: Vec =
(0..50).map(|_| StacksPrivateKey::random()).collect();
pub static ref POX_ADDRS: Vec = (0..50u64)
@@ -333,26 +325,6 @@ pub fn test_sim_hash_to_fork(in_bytes: &[u8; 32]) -> Option {
}
}
-#[cfg(test)]
-fn check_arithmetic_only(contract: &str, version: ClarityVersion) {
- let analysis = mem_type_check(contract, version, StacksEpochId::latest())
- .unwrap()
- .1;
- ArithmeticOnlyChecker::run(&analysis).expect("Should pass arithmetic checks");
-}
-
-#[test]
-fn cost_contract_is_arithmetic_only() {
- use crate::chainstate::stacks::boot::BOOT_CODE_COSTS;
- check_arithmetic_only(BOOT_CODE_COSTS, ClarityVersion::Clarity1);
-}
-
-#[test]
-fn cost_2_contract_is_arithmetic_only() {
- use crate::chainstate::stacks::boot::BOOT_CODE_COSTS_2;
- check_arithmetic_only(BOOT_CODE_COSTS_2, ClarityVersion::Clarity2);
-}
-
impl BurnStateDB for TestSimBurnStateDB {
fn get_tip_burn_block_height(&self) -> Option {
Some(self.height)
@@ -2481,664 +2453,3 @@ fn delegation_tests() {
);
});
}
-
-#[test]
-fn test_vote_withdrawal() {
- let mut sim = ClarityTestSim::new();
-
- sim.execute_next_block(|env| {
- env.initialize_versioned_contract(
- COST_VOTING_CONTRACT_TESTNET.clone(),
- ClarityVersion::Clarity1,
- &BOOT_CODE_COST_VOTING,
- None,
- )
- .unwrap();
-
- // Submit a proposal
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "submit-proposal",
- &symbols_from_values(vec![
- Value::Principal(
- PrincipalData::parse_qualified_contract_principal(
- "ST000000000000000000002AMW42H.function-name"
- )
- .unwrap()
- ),
- Value::string_ascii_from_bytes("function-name".into()).unwrap(),
- Value::Principal(
- PrincipalData::parse_qualified_contract_principal(
- "ST000000000000000000002AMW42H.cost-function-name"
- )
- .unwrap()
- ),
- Value::string_ascii_from_bytes("cost-function-name".into()).unwrap(),
- ])
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: true,
- data: Value::UInt(0).into()
- })
- );
-
- // Vote on the proposal
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "vote-proposal",
- &symbols_from_values(vec![Value::UInt(0), Value::UInt(10)]),
- )
- .unwrap();
-
- // Assert that the number of votes is correct
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "get-proposal-votes",
- &symbols_from_values(vec![Value::UInt(0)])
- )
- .unwrap()
- .0,
- Value::Optional(OptionalData {
- data: Some(Box::from(Value::UInt(10)))
- })
- );
-
- // Vote again on the proposal
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "vote-proposal",
- &symbols_from_values(vec![Value::UInt(0), Value::UInt(5)]),
- )
- .unwrap();
-
- // Assert that the number of votes is correct
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "get-proposal-votes",
- &symbols_from_values(vec![Value::UInt(0)])
- )
- .unwrap()
- .0,
- Value::Optional(OptionalData {
- data: Some(Box::from(Value::UInt(15)))
- })
- );
-
- // Assert votes are assigned to principal
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "get-principal-votes",
- &symbols_from_values(vec![
- Value::Principal(StandardPrincipalData::from(&USER_KEYS[0]).into()),
- Value::UInt(0),
- ])
- )
- .unwrap()
- .0,
- Value::Optional(OptionalData {
- data: Some(Box::from(Value::UInt(15)))
- })
- );
-
- // Assert withdrawal fails if amount is more than voted
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "withdraw-votes",
- &symbols_from_values(vec![Value::UInt(0), Value::UInt(20)]),
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: false,
- data: Value::Int(5).into()
- })
- );
-
- // Withdraw votes
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "withdraw-votes",
- &symbols_from_values(vec![Value::UInt(0), Value::UInt(5)]),
- )
- .unwrap();
-
- // Assert withdrawal worked
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "get-proposal-votes",
- &symbols_from_values(vec![Value::UInt(0)])
- )
- .unwrap()
- .0,
- Value::Optional(OptionalData {
- data: Some(Box::from(Value::UInt(10)))
- })
- );
- });
-
- // Fast forward to proposal expiration
- for _ in 0..2016 {
- sim.execute_next_block(|_| {});
- }
-
- sim.execute_next_block(|env| {
- // Withdraw STX after proposal expires
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "withdraw-votes",
- &symbols_from_values(vec![Value::UInt(0), Value::UInt(10)]),
- )
- .unwrap();
- });
-
- sim.execute_next_block(|env| {
- // Assert that stx balance is correct
- assert_eq!(
- env.eval_read_only(
- &COST_VOTING_CONTRACT_TESTNET,
- &format!("(stx-get-balance '{})", &Value::from(&USER_KEYS[0]))
- )
- .unwrap()
- .0,
- Value::UInt(1000000)
- );
- });
-}
-
-#[test]
-fn test_vote_fail() {
- let mut sim = ClarityTestSim::new();
-
- // Test voting in a proposal
- sim.execute_next_block(|env| {
- env.initialize_contract(
- COST_VOTING_CONTRACT_TESTNET.clone(),
- &BOOT_CODE_COST_VOTING,
- None,
- )
- .unwrap();
-
- // Submit a proposal
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "submit-proposal",
- &symbols_from_values(vec![
- Value::Principal(
- PrincipalData::parse_qualified_contract_principal(
- "ST000000000000000000002AMW42H.function-name2"
- )
- .unwrap()
- ),
- Value::string_ascii_from_bytes("function-name2".into()).unwrap(),
- Value::Principal(
- PrincipalData::parse_qualified_contract_principal(
- "ST000000000000000000002AMW42H.cost-function-name2"
- )
- .unwrap()
- ),
- Value::string_ascii_from_bytes("cost-function-name2".into()).unwrap(),
- ])
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: true,
- data: Value::UInt(0).into()
- })
- );
-
- // Assert confirmation fails
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "confirm-votes",
- &symbols_from_values(vec![Value::UInt(0)])
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: false,
- data: Value::Int(11).into()
- })
- );
-
- // Assert voting with more STX than are in an account fails
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "vote-proposal",
- &symbols_from_values(vec![Value::UInt(0), Value::UInt(USTX_PER_HOLDER + 1)]),
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: false,
- data: Value::Int(5).into()
- })
- );
-
- // Commit all liquid stacks to vote
- for user in USER_KEYS.iter() {
- env.execute_transaction(
- user.into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "vote-proposal",
- &symbols_from_values(vec![Value::UInt(0), Value::UInt(USTX_PER_HOLDER)]),
- )
- .unwrap();
- }
-
- // Assert confirmation returns true
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "confirm-votes",
- &symbols_from_values(vec![Value::UInt(0)])
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: true,
- data: Value::Bool(true).into()
- })
- );
- });
-
- sim.execute_next_block(|env| {
- env.execute_transaction(
- (&MINER_KEY.clone()).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "veto",
- &symbols_from_values(vec![Value::UInt(0)]),
- )
- .unwrap();
-
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "get-proposal-vetos",
- &symbols_from_values(vec![Value::UInt(0)])
- )
- .unwrap()
- .0,
- Value::Optional(OptionalData {
- data: Some(Box::from(Value::UInt(1)))
- })
- );
- });
-
- let fork_start = sim.block_height;
-
- for i in 0..25 {
- sim.execute_next_block(|env| {
- env.execute_transaction(
- (&MINER_KEY.clone()).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "veto",
- &symbols_from_values(vec![Value::UInt(0)]),
- )
- .unwrap();
-
- // assert error if already vetoed in this block
- assert_eq!(
- env.execute_transaction(
- (&MINER_KEY.clone()).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "veto",
- &symbols_from_values(vec![Value::UInt(0)])
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: false,
- data: Value::Int(9).into()
- })
- );
- })
- }
-
- for _ in 0..100 {
- sim.execute_next_block(|_| {});
- }
-
- sim.execute_next_block(|env| {
- // Assert confirmation fails because of majority veto
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "confirm-miners",
- &symbols_from_values(vec![Value::UInt(0)])
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: false,
- data: Value::Int(14).into()
- })
- );
- });
-
- // let's fork, and overcome the veto
- sim.execute_block_as_fork(fork_start, |_| {});
- for _ in 0..125 {
- sim.execute_next_block(|_| {});
- }
-
- sim.execute_next_block(|env| {
- // Assert confirmation passes because there are no vetos
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "confirm-miners",
- &symbols_from_values(vec![Value::UInt(0)])
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: true,
- data: Value::Bool(true).into(),
- })
- );
- });
-}
-
-#[test]
-fn test_vote_confirm() {
- let mut sim = ClarityTestSim::new();
-
- sim.execute_next_block(|env| {
- env.initialize_contract(
- COST_VOTING_CONTRACT_TESTNET.clone(),
- &BOOT_CODE_COST_VOTING,
- None,
- )
- .unwrap();
-
- // Submit a proposal
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "submit-proposal",
- &symbols_from_values(vec![
- Value::Principal(
- PrincipalData::parse_qualified_contract_principal(
- "ST000000000000000000002AMW42H.function-name2"
- )
- .unwrap()
- ),
- Value::string_ascii_from_bytes("function-name2".into()).unwrap(),
- Value::Principal(
- PrincipalData::parse_qualified_contract_principal(
- "ST000000000000000000002AMW42H.cost-function-name2"
- )
- .unwrap()
- ),
- Value::string_ascii_from_bytes("cost-function-name2".into()).unwrap(),
- ])
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: true,
- data: Value::UInt(0).into()
- })
- );
-
- // Assert confirmation fails
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "confirm-votes",
- &symbols_from_values(vec![Value::UInt(0)])
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: false,
- data: Value::Int(11).into()
- })
- );
-
- // Commit all liquid stacks to vote
- for user in USER_KEYS.iter() {
- env.execute_transaction(
- user.into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "vote-proposal",
- &symbols_from_values(vec![Value::UInt(0), Value::UInt(USTX_PER_HOLDER)]),
- )
- .unwrap();
- }
-
- // Assert confirmation returns true
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "confirm-votes",
- &symbols_from_values(vec![Value::UInt(0)])
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: true,
- data: Value::Bool(true).into()
- })
- );
- });
-
- // Fast forward to proposal expiration
- for _ in 0..2016 {
- sim.execute_next_block(|_| {});
- }
-
- for _ in 0..1007 {
- sim.execute_next_block(|_| {});
- }
-
- sim.execute_next_block(|env| {
- // Assert confirmation passes
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "confirm-miners",
- &symbols_from_values(vec![Value::UInt(0)])
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: true,
- data: Value::Bool(true).into()
- })
- );
- });
-}
-
-#[test]
-fn test_vote_too_many_confirms() {
- let mut sim = ClarityTestSim::new();
-
- let MAX_CONFIRMATIONS_PER_BLOCK = 10;
- sim.execute_next_block(|env| {
- env.initialize_contract(
- COST_VOTING_CONTRACT_TESTNET.clone(),
- &BOOT_CODE_COST_VOTING,
- None,
- )
- .unwrap();
-
- // Submit a proposal
- for i in 0..(MAX_CONFIRMATIONS_PER_BLOCK + 1) {
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "submit-proposal",
- &symbols_from_values(vec![
- Value::Principal(
- PrincipalData::parse_qualified_contract_principal(
- "ST000000000000000000002AMW42H.function-name2"
- )
- .unwrap()
- ),
- Value::string_ascii_from_bytes("function-name2".into()).unwrap(),
- Value::Principal(
- PrincipalData::parse_qualified_contract_principal(
- "ST000000000000000000002AMW42H.cost-function-name2"
- )
- .unwrap()
- ),
- Value::string_ascii_from_bytes("cost-function-name2".into()).unwrap(),
- ])
- )
- .unwrap()
- .0,
- Value::Response(ResponseData {
- committed: true,
- data: Value::UInt(i).into()
- })
- );
- }
-
- for i in 0..(MAX_CONFIRMATIONS_PER_BLOCK + 1) {
- // Commit all liquid stacks to vote
- for user in USER_KEYS.iter() {
- assert_eq!(
- env.execute_transaction(
- user.into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "vote-proposal",
- &symbols_from_values(vec![Value::UInt(i), Value::UInt(USTX_PER_HOLDER)]),
- )
- .unwrap()
- .0,
- Value::okay_true()
- );
- }
-
- // Assert confirmation returns true
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "confirm-votes",
- &symbols_from_values(vec![Value::UInt(i)])
- )
- .unwrap()
- .0,
- Value::okay_true(),
- );
-
- // withdraw
- for user in USER_KEYS.iter() {
- env.execute_transaction(
- user.into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "withdraw-votes",
- &symbols_from_values(vec![Value::UInt(i), Value::UInt(USTX_PER_HOLDER)]),
- )
- .unwrap();
- }
- }
- });
-
- // Fast forward to proposal expiration
- for _ in 0..2016 {
- sim.execute_next_block(|_| {});
- }
-
- for _ in 0..1007 {
- sim.execute_next_block(|_| {});
- }
-
- sim.execute_next_block(|env| {
- for i in 0..MAX_CONFIRMATIONS_PER_BLOCK {
- // Assert confirmation passes
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "confirm-miners",
- &symbols_from_values(vec![Value::UInt(i)])
- )
- .unwrap()
- .0,
- Value::okay_true(),
- );
- }
-
- // Assert next confirmation fails
- assert_eq!(
- env.execute_transaction(
- (&USER_KEYS[0]).into(),
- None,
- COST_VOTING_CONTRACT_TESTNET.clone(),
- "confirm-miners",
- &symbols_from_values(vec![Value::UInt(MAX_CONFIRMATIONS_PER_BLOCK)])
- )
- .unwrap()
- .0,
- Value::error(Value::Int(17)).unwrap()
- );
- });
-}
diff --git a/stackslib/src/clarity_vm/tests/costs.rs b/stackslib/src/clarity_vm/tests/costs.rs
index 7e2b782c0fc..cdb7361e96c 100644
--- a/stackslib/src/clarity_vm/tests/costs.rs
+++ b/stackslib/src/clarity_vm/tests/costs.rs
@@ -14,45 +14,33 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-use clarity::vm::clarity::TransactionConnection;
use clarity::vm::contexts::{AssetMap, OwnedEnvironment};
use clarity::vm::costs::cost_functions::ClarityCostFunction;
use clarity::vm::costs::{
- compute_cost, ClarityCostFunctionEvaluator, ClarityCostFunctionReference, CostErrors,
- DefaultVersion, ExecutionCost, LimitedCostTracker, COSTS_1_NAME, COSTS_2_NAME, COSTS_3_NAME,
- COSTS_4_NAME,
+ compute_cost, ClarityCostFunctionReference, CostErrors, DefaultVersion, ExecutionCost,
+ LimitedCostTracker, COSTS_1_NAME, COSTS_2_NAME, COSTS_3_NAME, COSTS_4_NAME,
};
-use clarity::vm::database::clarity_db::ClarityDatabase;
use clarity::vm::errors::VmExecutionError;
use clarity::vm::events::StacksTransactionEvent;
use clarity::vm::functions::NativeFunctions;
use clarity::vm::representations::SymbolicExpression;
use clarity::vm::test_util::{
- execute, execute_on_network, generate_test_burn_state_db, symbols_from_values,
- TEST_BURN_STATE_DB, TEST_BURN_STATE_DB_21, TEST_HEADER_DB,
+ execute, generate_test_burn_state_db, symbols_from_values, TEST_BURN_STATE_DB, TEST_HEADER_DB,
};
use clarity::vm::tests::test_only_mainnet_to_chain_id;
use clarity::vm::types::{
PrincipalData, QualifiedContractIdentifier, StandardPrincipalData, Value,
};
-use clarity::vm::{ClarityName, ClarityVersion, ContractName};
-use lazy_static::lazy_static;
+use clarity::vm::{ClarityVersion, ContractName};
use stacks_common::types::chainstate::StacksBlockId;
use stacks_common::types::StacksEpochId;
use crate::chainstate::stacks::index::ClarityMarfTrieId;
-use crate::clarity_vm::clarity::{ClarityInstance, ClarityMarfStore, ClarityMarfStoreTransaction};
+use crate::clarity_vm::clarity::{ClarityInstance, ClarityMarfStore};
use crate::clarity_vm::database::marf::MarfedKV;
use crate::core::{FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH};
use crate::util_lib::boot::boot_code_id;
-lazy_static! {
- static ref COST_VOTING_MAINNET_CONTRACT: QualifiedContractIdentifier =
- boot_code_id("cost-voting", true);
- static ref COST_VOTING_TESTNET_CONTRACT: QualifiedContractIdentifier =
- boot_code_id("cost-voting", false);
-}
-
pub fn get_simple_test(function: &NativeFunctions) -> Option<&'static str> {
use clarity::vm::functions::NativeFunctions::*;
let s = match function {
@@ -321,228 +309,6 @@ where
))
}
-fn cost_voting_contract(use_mainnet: bool) -> &'static QualifiedContractIdentifier {
- if use_mainnet {
- &COST_VOTING_MAINNET_CONTRACT
- } else {
- &COST_VOTING_TESTNET_CONTRACT
- }
-}
-
-struct CostProposal {
- function_contract: PrincipalData,
- function_name: &'static str,
- cost_function_contract: PrincipalData,
- cost_function_name: &'static str,
-}
-
-fn cost_proposal(
- function_contract: impl Into,
- function_name: &'static str,
- cost_function_contract: impl Into,
- cost_function_name: &'static str,
-) -> CostProposal {
- CostProposal {
- function_contract: function_contract.into(),
- function_name,
- cost_function_contract: cost_function_contract.into(),
- cost_function_name,
- }
-}
-
-fn write_confirmed_cost_proposals(
- db: &mut ClarityDatabase,
- use_mainnet: bool,
- proposal_start_id: usize,
- confirmed_proposal_count: usize,
- proposals: impl IntoIterator
- ,
-) {
- let voting_contract_to_use = cost_voting_contract(use_mainnet);
- db.set_variable_unknown_descriptor(
- voting_contract_to_use,
- "confirmed-proposal-count",
- Value::UInt(confirmed_proposal_count as u128),
- )
- .unwrap();
-
- let epoch = db.get_clarity_epoch_version().unwrap();
- for (ix, proposal) in proposals.into_iter().enumerate() {
- let value = format!(
- "{{ function-contract: '{},
- function-name: \"{}\",
- cost-function-contract: '{},
- cost-function-name: \"{}\",
- confirmed-height: u1 }}",
- proposal.function_contract,
- proposal.function_name,
- proposal.cost_function_contract,
- proposal.cost_function_name
- );
- db.set_entry_unknown_descriptor(
- voting_contract_to_use,
- "confirmed-proposals",
- execute_on_network(
- &format!("{{ confirmed-id: u{} }}", ix + proposal_start_id),
- use_mainnet,
- ),
- execute_on_network(&value, use_mainnet),
- &epoch,
- )
- .unwrap();
- }
-}
-
-fn cost_voting_test_cost_definer(use_mainnet: bool) -> QualifiedContractIdentifier {
- let p1 = execute_on_network("'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR", use_mainnet);
- let Value::Principal(PrincipalData::Standard(p1_principal)) = p1 else {
- panic!("Expected a standard principal data");
- };
- QualifiedContractIdentifier::new(p1_principal, ContractName::from_literal("cost-definer"))
-}
-
-fn deploy_cost_voting_test_cost_definer(
- clarity_instance: &mut ClarityInstance,
- tip: &mut StacksBlockId,
- block_id_byte: &mut u8,
- use_mainnet: bool,
-) -> QualifiedContractIdentifier {
- let cost_definer = cost_voting_test_cost_definer(use_mainnet);
- let cost_definer_src = "
- (define-read-only (cost-definition-le (size uint))
- {
- runtime: u0, write_length: u0, write_count: u0, read_count: u0, read_length: u0
- })
- ";
- let burn_state_db = generate_test_burn_state_db(StacksEpochId::Epoch20);
- let next_block = next_test_block_id(block_id_byte);
- let mut block_conn =
- clarity_instance.begin_block(tip, &next_block, &TEST_HEADER_DB, &burn_state_db);
- block_conn.as_transaction(|tx| {
- let (ast, analysis) = tx
- .analyze_smart_contract(&cost_definer, ClarityVersion::Clarity1, cost_definer_src)
- .unwrap();
- tx.initialize_smart_contract(
- &cost_definer,
- ClarityVersion::Clarity1,
- &ast,
- cost_definer_src,
- None,
- |_, _| None,
- None,
- )
- .unwrap();
- tx.save_analysis(&cost_definer, &analysis).unwrap();
- });
- block_conn.commit_block();
- *tip = next_block;
-
- cost_definer
-}
-
-fn write_voted_le_cost_state(
- clarity_instance: &mut ClarityInstance,
- tip: &mut StacksBlockId,
- block_id_byte: &mut u8,
- vote_state_epoch: StacksEpochId,
- use_mainnet: bool,
- cost_definer: &QualifiedContractIdentifier,
-) {
- let burn_state_db = generate_test_burn_state_db(vote_state_epoch);
- let next_block = next_test_block_id(block_id_byte);
-
- let mut clarity_conn =
- clarity_instance.begin_block(tip, &next_block, &TEST_HEADER_DB, &burn_state_db);
- clarity_conn.as_transaction(|tx| {
- tx.with_clarity_db(|db| {
- db.set_clarity_epoch_version(vote_state_epoch)?;
- write_confirmed_cost_proposals(
- db,
- use_mainnet,
- 0,
- 1,
- [cost_proposal(
- boot_code_id("costs", use_mainnet),
- "cost_le",
- cost_definer.clone(),
- "cost-definition-le",
- )],
- );
- Ok(())
- })
- .unwrap();
- });
- clarity_conn.commit_block();
- *tip = next_block;
-}
-
-fn tracker_with_voted_le_cost_state(
- load_epoch: StacksEpochId,
- vote_state_epoch: StacksEpochId,
- use_mainnet: bool,
-) -> (LimitedCostTracker, QualifiedContractIdentifier) {
- let (mut clarity_instance, mut tip, mut block_id_byte) =
- new_cost_test_clarity_instance(use_mainnet);
- let cost_definer = deploy_cost_voting_test_cost_definer(
- &mut clarity_instance,
- &mut tip,
- &mut block_id_byte,
- use_mainnet,
- );
-
- let max_setup_epoch = std::cmp::max(load_epoch, vote_state_epoch);
- let mut vote_state_written = false;
-
- for setup_epoch in COST_TEST_SETUP_EPOCHS {
- if !vote_state_written && vote_state_epoch < setup_epoch {
- write_voted_le_cost_state(
- &mut clarity_instance,
- &mut tip,
- &mut block_id_byte,
- vote_state_epoch,
- use_mainnet,
- &cost_definer,
- );
- vote_state_written = true;
- }
-
- if max_setup_epoch < setup_epoch {
- break;
- }
-
- setup_cost_test_epoch(
- &mut clarity_instance,
- &mut tip,
- &mut block_id_byte,
- setup_epoch,
- );
- }
-
- if !vote_state_written {
- write_voted_le_cost_state(
- &mut clarity_instance,
- &mut tip,
- &mut block_id_byte,
- vote_state_epoch,
- use_mainnet,
- &cost_definer,
- );
- }
-
- let mut marf_kv = clarity_instance.destroy();
-
- let burn_state_db = generate_test_burn_state_db(load_epoch);
- let final_block = next_test_block_id(&mut block_id_byte);
- let mut store = marf_kv.begin(&tip, &final_block);
- let owned_env = OwnedEnvironment::new_max_limit(
- store.as_clarity_db(&TEST_HEADER_DB, &burn_state_db),
- load_epoch,
- use_mainnet,
- );
- let (_db, tracker) = owned_env.destruct().unwrap();
-
- (tracker, cost_definer)
-}
-
fn exec_cost(contract: &str, use_mainnet: bool, epoch: StacksEpochId) -> ExecutionCost {
let p1 = execute("'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR");
let Value::Principal(PrincipalData::Standard(p1_principal)) = p1.clone() else {
@@ -1482,707 +1248,6 @@ fn epoch_33_test_all_testnet() {
epoch_33_test_all(false)
}
-fn test_cost_contract_short_circuits(use_mainnet: bool, clarity_version: ClarityVersion) {
- let marf_kv = MarfedKV::temporary();
- let chain_id = test_only_mainnet_to_chain_id(use_mainnet);
- let mut clarity_instance = ClarityInstance::new(use_mainnet, chain_id, marf_kv);
- let burn_db = if clarity_version == ClarityVersion::Clarity2 {
- &TEST_BURN_STATE_DB_21
- } else {
- &TEST_BURN_STATE_DB
- };
-
- clarity_instance
- .begin_test_genesis_block(
- &StacksBlockId::sentinel(),
- &StacksBlockId::new(&FIRST_BURNCHAIN_CONSENSUS_HASH, &FIRST_STACKS_BLOCK_HASH),
- &TEST_HEADER_DB,
- burn_db,
- )
- .commit_block();
-
- let marf_kv = clarity_instance.destroy();
-
- let p1 = execute_on_network("'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR", use_mainnet);
- let p2 = execute_on_network("'SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G", use_mainnet);
-
- let Value::Principal(PrincipalData::Standard(p1_principal)) = p1.clone() else {
- panic!("Expected a standard principal data");
- };
-
- let Value::Principal(p2_principal) = p2.clone() else {
- panic!("Expected a principal data");
- };
-
- let cost_definer = QualifiedContractIdentifier::new(
- p1_principal.clone(),
- ContractName::from_literal("cost-definer"),
- );
- let intercepted = QualifiedContractIdentifier::new(
- p1_principal.clone(),
- ContractName::from_literal("intercepted"),
- );
- let caller =
- QualifiedContractIdentifier::new(p1_principal, ContractName::from_literal("caller"));
-
- let mut marf_kv = {
- let mut clarity_inst = ClarityInstance::new(use_mainnet, chain_id, marf_kv);
- let mut block_conn = clarity_inst.begin_block(
- &StacksBlockId::new(&FIRST_BURNCHAIN_CONSENSUS_HASH, &FIRST_STACKS_BLOCK_HASH),
- &StacksBlockId([1; 32]),
- &TEST_HEADER_DB,
- burn_db,
- );
-
- let cost_definer_src = "
- (define-read-only (cost-definition (size uint))
- {
- runtime: u1, write_length: u1, write_count: u1, read_count: u1, read_length: u1
- })
- ";
-
- let intercepted_src = "
- (define-read-only (intercepted-function (a uint))
- (if (>= a u10)
- (+ (+ a a) (+ a a)
- (+ a a) (+ a a))
- u0))
- ";
-
- let caller_src = "
- (define-public (execute (a uint))
- (ok (contract-call? .intercepted intercepted-function a)))
- ";
-
- for (contract_name, contract_src) in [
- (&cost_definer, cost_definer_src),
- (&intercepted, intercepted_src),
- (&caller, caller_src),
- ]
- .iter()
- {
- block_conn.as_transaction(|tx| {
- let (ast, analysis) = tx
- .analyze_smart_contract(contract_name, clarity_version, contract_src)
- .unwrap();
- tx.initialize_smart_contract(
- contract_name,
- clarity_version,
- &ast,
- contract_src,
- None,
- |_, _| None,
- None,
- )
- .unwrap();
- tx.save_analysis(contract_name, &analysis).unwrap();
- });
- }
-
- block_conn.commit_block();
- clarity_inst.destroy()
- };
-
- let without_interposing_5 = {
- let mut store = marf_kv.begin(&StacksBlockId([1; 32]), &StacksBlockId([2; 32]));
- let mut owned_env = OwnedEnvironment::new_max_limit(
- store.as_clarity_db(&TEST_HEADER_DB, burn_db),
- StacksEpochId::Epoch20,
- use_mainnet,
- );
-
- execute_transaction(
- &mut owned_env,
- p2_principal.clone(),
- &caller,
- "execute",
- &symbols_from_values(vec![Value::UInt(5)]),
- )
- .unwrap();
-
- let (_db, tracker) = owned_env.destruct().unwrap();
-
- store.test_commit();
- tracker.get_total()
- };
-
- let without_interposing_10 = {
- let mut store = marf_kv.begin(&StacksBlockId([2; 32]), &StacksBlockId([3; 32]));
- let mut owned_env = OwnedEnvironment::new_max_limit(
- store.as_clarity_db(&TEST_HEADER_DB, burn_db),
- StacksEpochId::Epoch20,
- use_mainnet,
- );
-
- execute_transaction(
- &mut owned_env,
- p2_principal.clone(),
- &caller,
- "execute",
- &symbols_from_values(vec![Value::UInt(10)]),
- )
- .unwrap();
-
- let (_db, tracker) = owned_env.destruct().unwrap();
-
- store.test_commit();
- tracker.get_total()
- };
-
- let voting_contract_to_use: &QualifiedContractIdentifier = if use_mainnet {
- &COST_VOTING_MAINNET_CONTRACT
- } else {
- &COST_VOTING_TESTNET_CONTRACT
- };
-
- {
- let mut store = marf_kv.begin(&StacksBlockId([3; 32]), &StacksBlockId([4; 32]));
- let mut db = store.as_clarity_db(&TEST_HEADER_DB, burn_db);
- db.begin();
- db.set_variable_unknown_descriptor(
- voting_contract_to_use,
- "confirmed-proposal-count",
- Value::UInt(1),
- )
- .unwrap();
- let value = format!(
- "{{ function-contract: '{},
- function-name: {},
- cost-function-contract: '{},
- cost-function-name: {},
- confirmed-height: u1 }}",
- intercepted, "\"intercepted-function\"", cost_definer, "\"cost-definition\""
- );
- let epoch = db.get_clarity_epoch_version().unwrap();
- db.set_entry_unknown_descriptor(
- voting_contract_to_use,
- "confirmed-proposals",
- execute_on_network("{ confirmed-id: u0 }", use_mainnet),
- execute_on_network(&value, use_mainnet),
- &epoch,
- )
- .unwrap();
- db.commit().unwrap();
- store.test_commit();
- }
-
- let with_interposing_5 = {
- let mut store = marf_kv.begin(&StacksBlockId([4; 32]), &StacksBlockId([5; 32]));
-
- let mut owned_env = OwnedEnvironment::new_max_limit(
- store.as_clarity_db(&TEST_HEADER_DB, burn_db),
- StacksEpochId::Epoch20,
- use_mainnet,
- );
-
- execute_transaction(
- &mut owned_env,
- p2_principal.clone(),
- &caller,
- "execute",
- &symbols_from_values(vec![Value::UInt(5)]),
- )
- .unwrap();
-
- let (_db, tracker) = owned_env.destruct().unwrap();
-
- store.test_commit();
- tracker.get_total()
- };
-
- let with_interposing_10 = {
- let mut store = marf_kv.begin(&StacksBlockId([5; 32]), &StacksBlockId([6; 32]));
- let mut owned_env = OwnedEnvironment::new_max_limit(
- store.as_clarity_db(&TEST_HEADER_DB, burn_db),
- StacksEpochId::Epoch20,
- use_mainnet,
- );
-
- execute_transaction(
- &mut owned_env,
- p2_principal,
- &caller,
- "execute",
- &symbols_from_values(vec![Value::UInt(10)]),
- )
- .unwrap();
-
- let (_db, tracker) = owned_env.destruct().unwrap();
-
- tracker.get_total()
- };
-
- assert!(without_interposing_5.exceeds(&with_interposing_5));
- assert!(without_interposing_10.exceeds(&with_interposing_10));
-
- assert_eq!(with_interposing_5, with_interposing_10);
- assert!(without_interposing_5 != without_interposing_10);
-}
-
-#[test]
-fn test_cost_contract_short_circuits_mainnet() {
- test_cost_contract_short_circuits(true, ClarityVersion::Clarity1);
- test_cost_contract_short_circuits(true, ClarityVersion::Clarity2);
-}
-
-#[test]
-fn test_cost_contract_short_circuits_testnet() {
- test_cost_contract_short_circuits(false, ClarityVersion::Clarity1);
- test_cost_contract_short_circuits(false, ClarityVersion::Clarity2);
-}
-
-fn test_cost_voting_integration(use_mainnet: bool, clarity_version: ClarityVersion) {
- let marf_kv = MarfedKV::temporary();
- let chain_id = test_only_mainnet_to_chain_id(use_mainnet);
- let mut clarity_instance = ClarityInstance::new(use_mainnet, chain_id, marf_kv);
- let burn_db = if clarity_version == ClarityVersion::Clarity2 {
- &TEST_BURN_STATE_DB_21
- } else {
- &TEST_BURN_STATE_DB
- };
-
- clarity_instance
- .begin_test_genesis_block(
- &StacksBlockId::sentinel(),
- &StacksBlockId::new(&FIRST_BURNCHAIN_CONSENSUS_HASH, &FIRST_STACKS_BLOCK_HASH),
- &TEST_HEADER_DB,
- burn_db,
- )
- .commit_block();
-
- let marf_kv = clarity_instance.destroy();
-
- let p1 = execute("'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR");
- let p2 = execute("'SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G");
-
- let Value::Principal(PrincipalData::Standard(p1_principal)) = p1.clone() else {
- panic!("Expected a standard principal data");
- };
-
- let Value::Principal(p2_principal) = p2.clone() else {
- panic!("Expected a principal data");
- };
-
- let cost_definer = QualifiedContractIdentifier::new(
- p1_principal.clone(),
- ContractName::from_literal("cost-definer"),
- );
- let bad_cost_definer = QualifiedContractIdentifier::new(
- p1_principal.clone(),
- ContractName::from_literal("bad-cost-definer"),
- );
- let bad_cost_args_definer = QualifiedContractIdentifier::new(
- p1_principal.clone(),
- ContractName::from_literal("bad-cost-args-definer"),
- );
- let intercepted = QualifiedContractIdentifier::new(
- p1_principal.clone(),
- ContractName::from_literal("intercepted"),
- );
- let caller = QualifiedContractIdentifier::new(
- p1_principal.clone(),
- ContractName::from_literal("caller"),
- );
-
- let mut marf_kv = {
- let mut clarity_inst = ClarityInstance::new(use_mainnet, chain_id, marf_kv);
- let mut block_conn = clarity_inst.begin_block(
- &StacksBlockId::new(&FIRST_BURNCHAIN_CONSENSUS_HASH, &FIRST_STACKS_BLOCK_HASH),
- &StacksBlockId([1; 32]),
- &TEST_HEADER_DB,
- burn_db,
- );
-
- let cost_definer_src = "
- (define-read-only (cost-definition (size uint))
- {
- runtime: u1, write_length: u1, write_count: u1, read_count: u1, read_length: u1
- })
- (define-read-only (cost-definition-le (size uint))
- {
- runtime: u0, write_length: u0, write_count: u0, read_count: u0, read_length: u0
- })
- (define-read-only (cost-definition-multi-arg (a uint) (b uint) (c uint))
- {
- runtime: u1, write_length: u0, write_count: u0, read_count: u0, read_length: u0
- })
-
- ";
-
- let bad_cost_definer_src = "
- (define-data-var my-var uint u10)
- (define-read-only (cost-definition (size uint))
- {
- runtime: (var-get my-var), write_length: u1, write_count: u1, read_count: u1, read_length: u1
- })
- ";
-
- let bad_cost_args_definer_src = "
- (define-read-only (cost-definition (a uint) (b uint))
- {
- runtime: u1, write_length: u1, write_count: u1, read_count: u1, read_length: u1
- })
- ";
-
- let intercepted_src = "
- (define-read-only (intercepted-function (a uint))
- (if (>= a u10)
- (+ (+ a a) (+ a a)
- (+ a a) (+ a a))
- u0))
-
- (define-read-only (intercepted-function2 (a uint) (b uint) (c uint))
- (- (+ a b) c))
-
- (define-public (non-read-only) (ok (+ 1 2 3)))
- ";
-
- let caller_src = "
- (define-public (execute (a uint))
- (ok (contract-call? .intercepted intercepted-function a)))
- (define-public (execute-2 (a uint))
- (ok (< a a)))
- ";
-
- for (contract_name, contract_src) in [
- (&cost_definer, cost_definer_src),
- (&intercepted, intercepted_src),
- (&caller, caller_src),
- (&bad_cost_definer, bad_cost_definer_src),
- (&bad_cost_args_definer, bad_cost_args_definer_src),
- ]
- .iter()
- {
- block_conn.as_transaction(|tx| {
- let (ast, analysis) = tx
- .analyze_smart_contract(contract_name, clarity_version, contract_src)
- .unwrap();
- tx.initialize_smart_contract(
- contract_name,
- clarity_version,
- &ast,
- contract_src,
- None,
- |_, _| None,
- None,
- )
- .unwrap();
- tx.save_analysis(contract_name, &analysis).unwrap();
- });
- }
-
- block_conn.commit_block();
- clarity_inst.destroy()
- };
-
- let bad_cases = vec![
- // non existent "replacement target"
- cost_proposal(
- QualifiedContractIdentifier::local("non-existent").unwrap(),
- "non-existent-func",
- cost_definer.clone(),
- "cost-definition",
- ),
- // replacement target isn't a contract principal
- cost_proposal(
- p1_principal.clone(),
- "non-existent-func",
- cost_definer.clone(),
- "cost-definition",
- ),
- // cost defining contract isn't a contract principal
- cost_proposal(
- intercepted.clone(),
- "intercepted-function",
- p1_principal,
- "cost-definition",
- ),
- // replacement function doesn't exist
- cost_proposal(
- intercepted.clone(),
- "non-existent-func",
- cost_definer.clone(),
- "cost-definition",
- ),
- // replacement function isn't read-only
- cost_proposal(
- intercepted.clone(),
- "non-read-only",
- cost_definer.clone(),
- "cost-definition",
- ),
- // "boot cost" function doesn't exist
- cost_proposal(
- boot_code_id("costs", false),
- "non-existent-func",
- cost_definer.clone(),
- "cost-definition",
- ),
- // cost defining contract doesn't exist
- cost_proposal(
- intercepted.clone(),
- "intercepted-function",
- QualifiedContractIdentifier::local("non-existent").unwrap(),
- "cost-definition",
- ),
- // cost defining function doesn't exist
- cost_proposal(
- intercepted.clone(),
- "intercepted-function",
- cost_definer.clone(),
- "cost-definition-2",
- ),
- // cost defining contract isn't arithmetic-only
- cost_proposal(
- intercepted.clone(),
- "intercepted-function",
- bad_cost_definer,
- "cost-definition",
- ),
- // cost defining contract has incorrect number of arguments
- cost_proposal(
- intercepted.clone(),
- "intercepted-function",
- bad_cost_args_definer,
- "cost-definition",
- ),
- ];
-
- let bad_proposals = bad_cases.len();
-
- {
- let mut store = marf_kv.begin(&StacksBlockId([1; 32]), &StacksBlockId([2; 32]));
-
- let mut db = store.as_clarity_db(&TEST_HEADER_DB, burn_db);
- db.begin();
- write_confirmed_cost_proposals(&mut db, use_mainnet, 0, bad_proposals, bad_cases);
- db.commit().unwrap();
- store.test_commit();
- }
-
- let le_cost_without_interception = {
- let mut store = marf_kv.begin(&StacksBlockId([2; 32]), &StacksBlockId([3; 32]));
- let mut owned_env = OwnedEnvironment::new_max_limit(
- store.as_clarity_db(&TEST_HEADER_DB, burn_db),
- StacksEpochId::Epoch20,
- use_mainnet,
- );
-
- execute_transaction(
- &mut owned_env,
- p2_principal.clone(),
- &caller,
- "execute-2",
- &symbols_from_values(vec![Value::UInt(5)]),
- )
- .unwrap();
-
- let (_db, tracker) = owned_env.destruct().unwrap();
-
- assert!(
- tracker.contract_call_circuits().is_empty(),
- "No contract call circuits should have been processed"
- );
- for (target, referenced_function) in tracker.cost_function_references().into_iter() {
- assert!(
- matches!(
- referenced_function,
- ClarityCostFunctionEvaluator::Default(_, _, DefaultVersion::Costs1)
- ),
- "All cost functions should still point to the boot costs"
- );
- }
- store.test_commit();
-
- tracker.get_total()
- };
-
- let good_cases = vec![
- cost_proposal(
- intercepted.clone(),
- "intercepted-function",
- cost_definer.clone(),
- "cost-definition",
- ),
- cost_proposal(
- boot_code_id("costs", use_mainnet),
- "cost_le",
- cost_definer.clone(),
- "cost-definition-le",
- ),
- cost_proposal(
- intercepted.clone(),
- "intercepted-function2",
- cost_definer.clone(),
- "cost-definition-multi-arg",
- ),
- ];
-
- {
- let mut store = marf_kv.begin(&StacksBlockId([3; 32]), &StacksBlockId([4; 32]));
-
- let mut db = store.as_clarity_db(&TEST_HEADER_DB, burn_db);
- db.begin();
-
- let good_proposals = good_cases.len();
- write_confirmed_cost_proposals(
- &mut db,
- use_mainnet,
- bad_proposals,
- bad_proposals + good_proposals,
- good_cases,
- );
- db.commit().unwrap();
-
- store.test_commit();
- }
-
- {
- let mut store = marf_kv.begin(&StacksBlockId([4; 32]), &StacksBlockId([5; 32]));
- let mut owned_env = OwnedEnvironment::new_max_limit(
- store.as_clarity_db(&TEST_HEADER_DB, burn_db),
- StacksEpochId::Epoch20,
- use_mainnet,
- );
-
- execute_transaction(
- &mut owned_env,
- p2_principal,
- &caller,
- "execute-2",
- &symbols_from_values(vec![Value::UInt(5)]),
- )
- .unwrap();
-
- let (_db, tracker) = owned_env.destruct().unwrap();
-
- // cost of `le` should be less now, because the proposal made it free
- assert!(le_cost_without_interception.exceeds(&tracker.get_total()));
-
- let circuits = tracker.contract_call_circuits();
- assert_eq!(circuits.len(), 2);
-
- let circuit1 = circuits.get(&(
- intercepted.clone(),
- ClarityName::from_literal("intercepted-function"),
- ));
- let circuit2 = circuits.get(&(
- intercepted,
- ClarityName::from_literal("intercepted-function2"),
- ));
-
- assert!(circuit1.is_some());
- assert!(circuit2.is_some());
-
- assert_eq!(circuit1.unwrap().contract_id, cost_definer);
- assert_eq!(circuit1.unwrap().function_name, "cost-definition");
-
- assert_eq!(circuit2.unwrap().contract_id, cost_definer);
- assert_eq!(circuit2.unwrap().function_name, "cost-definition-multi-arg");
-
- for (target, referenced_function) in tracker.cost_function_references().into_iter() {
- if target == &ClarityCostFunction::Le {
- let ClarityCostFunctionEvaluator::Clarity(referenced_function) =
- referenced_function
- else {
- panic!("Replaced function should be evaluated in Clarity");
- };
- assert_eq!(&referenced_function.contract_id, &cost_definer);
- assert_eq!(&referenced_function.function_name, "cost-definition-le");
- } else {
- assert!(
- matches!(
- referenced_function,
- ClarityCostFunctionEvaluator::Default(_, _, DefaultVersion::Costs1)
- ),
- "Cost function should still point to the boot costs"
- );
- }
- }
- store.test_commit();
- };
-}
-
-#[test]
-fn test_cost_voting_integration_mainnet() {
- test_cost_voting_integration(true, ClarityVersion::Clarity1);
- test_cost_voting_integration(true, ClarityVersion::Clarity2);
-}
-
-#[test]
-fn test_cost_voting_integration_testnet() {
- test_cost_voting_integration(false, ClarityVersion::Clarity1);
- test_cost_voting_integration(false, ClarityVersion::Clarity2);
-}
-
-/// Before epoch 4.0, confirmed `.cost-voting` state can replace boot cost functions.
-#[rstest::rstest]
-#[case::mainnet(true)]
-#[case::testnet(false)]
-fn cost_voting_state_applies_before_epoch_40(#[case] use_mainnet: bool) {
- let (tracker, cost_definer) = tracker_with_voted_le_cost_state(
- StacksEpochId::Epoch34,
- StacksEpochId::Epoch34,
- use_mainnet,
- );
-
- let cost_function_references = tracker.cost_function_references();
- assert_eq!(
- cost_function_references.len(),
- ClarityCostFunction::ALL.len(),
- "all cost functions must have a reference before cost-voting retires (mainnet={use_mainnet})"
- );
-
- let le_reference = cost_function_references
- .get(&ClarityCostFunction::Le)
- .expect("cost_le must have a reference");
- let ClarityCostFunctionEvaluator::Clarity(le_reference) = le_reference else {
- panic!("voted cost_le should be evaluated in Clarity before Epoch 4.0");
- };
- assert_eq!(&le_reference.contract_id, &cost_definer);
- assert_eq!(&le_reference.function_name, "cost-definition-le");
-}
-
-/// With `.cost-voting` disabled >= epoch 4.0, both existing and newly written voting state is
-/// ignored and every cost function resolves to the boot cost contract.
-#[rstest::rstest]
-#[case::mainnet_pre_40_voting_state(true, StacksEpochId::Epoch34)]
-#[case::testnet_pre_40_voting_state(false, StacksEpochId::Epoch34)]
-#[case::mainnet_post_40_voting_state(true, StacksEpochId::Epoch40)]
-#[case::testnet_post_40_voting_state(false, StacksEpochId::Epoch40)]
-fn cost_voting_state_is_ignored_at_epoch_40(
- #[case] use_mainnet: bool,
- #[case] vote_state_epoch: StacksEpochId,
-) {
- let (tracker, _cost_definer) =
- tracker_with_voted_le_cost_state(StacksEpochId::Epoch40, vote_state_epoch, use_mainnet);
-
- let cost_function_references = tracker.cost_function_references();
- assert_eq!(
- cost_function_references.len(),
- ClarityCostFunction::ALL.len(),
- "all cost functions must have a reference after cost-voting retires (mainnet={use_mainnet}, vote_state_epoch={vote_state_epoch})"
- );
- assert!(
- cost_function_references.values().all(|evaluator| matches!(
- evaluator,
- ClarityCostFunctionEvaluator::Default(_, _, DefaultVersion::Costs5)
- )),
- "all cost functions must use the epoch 4.0 boot cost contract after cost-voting retires (mainnet={use_mainnet}, vote_state_epoch={vote_state_epoch})"
- );
-
- let le_reference = cost_function_references
- .get(&ClarityCostFunction::Le)
- .expect("cost_le must have a reference");
- assert!(
- matches!(
- le_reference,
- ClarityCostFunctionEvaluator::Default(_, _, DefaultVersion::Costs5)
- ),
- "voted cost_le replacement must be ignored after cost-voting retires (mainnet={use_mainnet}, vote_state_epoch={vote_state_epoch})"
- );
-}
-
#[test]
fn test_cost_change() {
let use_mainnet = false;
From 57c6b16b73754ae8287b2f153989adfc246f383b Mon Sep 17 00:00:00 2001
From: Cyle Witruk <236413682+cylewitruk-stacks@users.noreply.github.com>
Date: Mon, 22 Jun 2026 15:50:43 +0200
Subject: [PATCH 5/5] add changelog entry
---
changelog.d/7342-cost-voting-functional-removal.removed | 1 +
1 file changed, 1 insertion(+)
create mode 100644 changelog.d/7342-cost-voting-functional-removal.removed
diff --git a/changelog.d/7342-cost-voting-functional-removal.removed b/changelog.d/7342-cost-voting-functional-removal.removed
new file mode 100644
index 00000000000..6b57e04fb42
--- /dev/null
+++ b/changelog.d/7342-cost-voting-functional-removal.removed
@@ -0,0 +1 @@
+Removes the functional elements of the `.cost-voting` contract per `SIP-044`
\ No newline at end of file