feat: block-height -> stacks-block-height

`block-height` can no longer be used in Clarity 3 and above and will
cause an analysis error. In its place is `stacks-block-height`.
This commit is contained in:
Brice Dobry
2024-04-30 10:54:59 -04:00
parent 807d54c07a
commit 5a91bdc13b
9 changed files with 183 additions and 20 deletions

View File

@@ -148,7 +148,7 @@ impl<'a> ArithmeticOnlyChecker<'a> {
{
match native_var {
ContractCaller | TxSender | TotalLiquidMicroSTX | BlockHeight | BurnBlockHeight
| Regtest | TxSponsor | Mainnet | ChainId => {
| Regtest | TxSponsor | Mainnet | ChainId | StacksBlockHeight | TenureHeight => {
Err(Error::VariableForbidden(native_var))
}
NativeNone | NativeTrue | NativeFalse => Ok(()),

View File

@@ -323,9 +323,9 @@ fn type_reserved_variable(variable_name: &str) -> CheckResult<Option<TypeSignatu
NativeFalse => TypeSignature::BoolType,
TotalLiquidMicroSTX => TypeSignature::UIntType,
Regtest => TypeSignature::BoolType,
TxSponsor | Mainnet | ChainId => {
TxSponsor | Mainnet | ChainId | StacksBlockHeight | TenureHeight => {
return Err(CheckErrors::Expects(
"tx-sponsor, mainnet, and chain-id should not reach here in 2.05".into(),
"tx-sponsor, mainnet, chain-id, stacks-block-height, and tenure-height should not reach here in 2.05".into(),
)
.into())
}

View File

@@ -42,7 +42,7 @@ impl TraitContext {
pub fn new(clarity_version: ClarityVersion) -> TraitContext {
match clarity_version {
ClarityVersion::Clarity1 => Self::Clarity1(HashMap::new()),
ClarityVersion::Clarity2 => Self::Clarity2 {
ClarityVersion::Clarity2 | ClarityVersion::Clarity3 => Self::Clarity2 {
defined: HashSet::new(),
all: HashMap::new(),
},

View File

@@ -858,6 +858,8 @@ fn type_reserved_variable(
.map_err(|_| CheckErrors::Expects("Bad construction".into()))?,
ContractCaller => TypeSignature::PrincipalType,
BlockHeight => TypeSignature::UIntType,
StacksBlockHeight => TypeSignature::UIntType,
TenureHeight => TypeSignature::UIntType,
BurnBlockHeight => TypeSignature::UIntType,
NativeNone => TypeSignature::new_option(no_type())
.map_err(|_| CheckErrors::Expects("Bad construction".into()))?,

View File

@@ -2740,7 +2740,9 @@ fn clarity_trait_experiments_downcast_literal_2(
})
.unwrap_err();
match version {
ClarityVersion::Clarity2 => assert!(err.starts_with("ExpectedCallableType(PrincipalType)")),
ClarityVersion::Clarity2 | ClarityVersion::Clarity3 => {
assert!(err.starts_with("ExpectedCallableType(PrincipalType)"))
}
ClarityVersion::Clarity1 => {
assert!(err.starts_with("TraitReferenceUnknown(\"principal-value\")"))
}
@@ -2935,7 +2937,9 @@ fn clarity_trait_experiments_trait_cast_incompatible(
assert!(err.starts_with("TypeError(CallableType(Trait(TraitIdentifier"))
}
}
ClarityVersion::Clarity2 => assert!(err.starts_with("IncompatibleTrait")),
ClarityVersion::Clarity2 | ClarityVersion::Clarity3 => {
assert!(err.starts_with("IncompatibleTrait"))
}
}
}

View File

@@ -126,18 +126,16 @@ to the same contract principal.",
example: "(print contract-caller) ;; Will print out a Stacks address of the transaction sender",
};
const STACKS_BLOCK_HEIGHT: KeywordAPI = KeywordAPI {
const STACKS_BLOCK_HEIGHT_KEYWORD: SimpleKeywordAPI = SimpleKeywordAPI {
name: "stacks-block-height",
snippet: "stacks-block-height",
output_type: "uint",
description: "Returns the current block height of the Stacks blockchain.",
example:
"(<= stacks-block-height u500000) ;; returns true if the current block-height has not passed 500,000 blocks.",
min_version: ClarityVersion::Clarity3,
max_version: None,
};
const TENURE_HEIGHT_KEYWORD: KeywordAPI = KeywordAPI {
const TENURE_HEIGHT_KEYWORD: SimpleKeywordAPI = SimpleKeywordAPI {
name: "tenure-height",
snippet: "tenure-height",
output_type: "uint",
@@ -145,8 +143,6 @@ const TENURE_HEIGHT_KEYWORD: KeywordAPI = KeywordAPI {
At the start of epoch 3.0, `tenure-height` will return the same value as `block-height`, then it will continue to increase as each tenures passes.",
example:
"(< tenure-height u140000) ;; returns true if the current tenure-height has passed 140,000 blocks.",
min_version: ClarityVersion::Clarity3,
max_version: None,
};
const TX_SENDER_KEYWORD: SimpleKeywordAPI = SimpleKeywordAPI {
@@ -2568,13 +2564,15 @@ pub fn make_api_reference(function: &NativeFunctions) -> FunctionAPI {
}
fn make_keyword_reference(variable: &NativeVariables) -> Option<KeywordAPI> {
let simple_api = match variable {
let keyword = match variable {
NativeVariables::TxSender => TX_SENDER_KEYWORD.clone(),
NativeVariables::ContractCaller => CONTRACT_CALLER_KEYWORD.clone(),
NativeVariables::NativeNone => NONE_KEYWORD.clone(),
NativeVariables::NativeTrue => TRUE_KEYWORD.clone(),
NativeVariables::NativeFalse => FALSE_KEYWORD.clone(),
NativeVariables::BlockHeight => BLOCK_HEIGHT.clone(),
NativeVariables::StacksBlockHeight => STACKS_BLOCK_HEIGHT_KEYWORD.clone(),
NativeVariables::TenureHeight => TENURE_HEIGHT_KEYWORD.clone(),
NativeVariables::BurnBlockHeight => BURN_BLOCK_HEIGHT.clone(),
NativeVariables::TotalLiquidMicroSTX => TOTAL_LIQUID_USTX_KEYWORD.clone(),
NativeVariables::Regtest => REGTEST_KEYWORD.clone(),
@@ -2583,11 +2581,11 @@ fn make_keyword_reference(variable: &NativeVariables) -> Option<KeywordAPI> {
NativeVariables::TxSponsor => TX_SPONSOR_KEYWORD.clone(),
};
Some(KeywordAPI {
name: simple_api.name,
snippet: simple_api.snippet,
output_type: simple_api.output_type,
description: simple_api.description,
example: simple_api.example,
name: keyword.name,
snippet: keyword.snippet,
output_type: keyword.output_type,
description: keyword.description,
example: keyword.example,
min_version: variable.get_min_version(),
max_version: variable.get_max_version(),
})

View File

@@ -33,6 +33,7 @@ mod sequences;
#[cfg(test)]
mod simple_apply_eval;
mod traits;
mod variables;
macro_rules! epochs_template {
($($epoch:ident,)*) => {
@@ -50,7 +51,6 @@ macro_rules! epochs_template {
match epoch {
// don't test Epoch-1.0
StacksEpochId::Epoch10 => (),
StacksEpochId::Epoch30 => (),
// this will lead to a compile time failure if an epoch is left out
// of the epochs_template! macro list
$(StacksEpochId::$epoch)|* => (),
@@ -76,10 +76,16 @@ macro_rules! clarity_template {
match (epoch, clarity) {
// don't test Epoch-1.0
(StacksEpochId::Epoch10, _) => (),
(StacksEpochId::Epoch30, _) => (),
// don't test these pairs, because they aren't supported:
(StacksEpochId::Epoch20, ClarityVersion::Clarity2) => (),
(StacksEpochId::Epoch2_05, ClarityVersion::Clarity2) => (),
(StacksEpochId::Epoch20, ClarityVersion::Clarity3) => (),
(StacksEpochId::Epoch2_05, ClarityVersion::Clarity3) => (),
(StacksEpochId::Epoch21, ClarityVersion::Clarity3) => (),
(StacksEpochId::Epoch22, ClarityVersion::Clarity3) => (),
(StacksEpochId::Epoch23, ClarityVersion::Clarity3) => (),
(StacksEpochId::Epoch24, ClarityVersion::Clarity3) => (),
(StacksEpochId::Epoch25, ClarityVersion::Clarity3) => (),
// this will lead to a compile time failure if a pair is left out
// of the clarity_template! macro list
$((StacksEpochId::$epoch, ClarityVersion::$clarity))|* => (),
@@ -103,6 +109,7 @@ epochs_template! {
Epoch23,
Epoch24,
Epoch25,
Epoch30,
}
clarity_template! {
@@ -118,6 +125,9 @@ clarity_template! {
(Epoch24, Clarity2),
(Epoch25, Clarity1),
(Epoch25, Clarity2),
(Epoch30, Clarity1),
(Epoch30, Clarity2),
(Epoch30, Clarity3),
}
#[cfg(test)]

View File

@@ -0,0 +1,147 @@
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
// Copyright (C) 2020-2024 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 <http://www.gnu.org/licenses/>.
#[cfg(any(test, feature = "testing"))]
use rstest::rstest;
use stacks_common::types::StacksEpochId;
#[cfg(test)]
use crate::vm::analysis::type_checker::v2_1::tests::contracts::type_check_version;
use crate::vm::analysis::{run_analysis, CheckError};
use crate::vm::ast::{parse, ASTRules};
use crate::vm::database::MemoryBackingStore;
use crate::vm::errors::{CheckErrors, Error};
use crate::vm::tests::{test_clarity_versions, tl_env_factory, TopLevelMemoryEnvironmentGenerator};
use crate::vm::types::{QualifiedContractIdentifier, Value};
use crate::vm::{ClarityVersion, ContractContext};
#[apply(test_clarity_versions)]
fn test_block_height(
version: ClarityVersion,
epoch: StacksEpochId,
mut tl_env_factory: TopLevelMemoryEnvironmentGenerator,
) {
let contract = "(define-read-only (test-func) block-height)";
let mut placeholder_context =
ContractContext::new(QualifiedContractIdentifier::transient(), version);
let mut owned_env = tl_env_factory.get_env(epoch);
let contract_identifier = QualifiedContractIdentifier::local("test-contract").unwrap();
let mut exprs = parse(&contract_identifier, &contract, version, epoch).unwrap();
let mut marf = MemoryBackingStore::new();
let mut db = marf.as_analysis_db();
let analysis = db.execute(|db| {
type_check_version(&contract_identifier, &mut exprs, db, true, epoch, version)
});
if version >= ClarityVersion::Clarity3 {
let err = analysis.unwrap_err();
assert_eq!(
CheckErrors::UndefinedVariable("block-height".to_string()),
err.err
);
} else {
assert!(analysis.is_ok());
}
// Initialize the contract
// Note that we're ignoring the analysis failure here so that we can test
// the runtime behavior. In Clarity 3, if this case somehow gets past the
// analysis, it should fail at runtime.
let result = owned_env.initialize_versioned_contract(
contract_identifier.clone(),
version,
contract,
None,
ASTRules::PrecheckSize,
);
let mut env = owned_env.get_exec_environment(None, None, &mut placeholder_context);
// Call the function
let eval_result = env.eval_read_only(&contract_identifier, "(test-func)");
// In Clarity 3, this should trigger a runtime error
if version >= ClarityVersion::Clarity3 {
let err = eval_result.unwrap_err();
assert_eq!(
Error::Unchecked(CheckErrors::UndefinedVariable("block-height".to_string(),)),
err
);
} else {
assert_eq!(Ok(Value::UInt(1)), eval_result);
}
}
#[apply(test_clarity_versions)]
fn test_stacks_block_height(
version: ClarityVersion,
epoch: StacksEpochId,
mut tl_env_factory: TopLevelMemoryEnvironmentGenerator,
) {
let contract = "(define-read-only (test-func) stacks-block-height)";
let mut placeholder_context =
ContractContext::new(QualifiedContractIdentifier::transient(), version);
let mut owned_env = tl_env_factory.get_env(epoch);
let contract_identifier = QualifiedContractIdentifier::local("test-contract").unwrap();
let mut exprs = parse(&contract_identifier, &contract, version, epoch).unwrap();
let mut marf = MemoryBackingStore::new();
let mut db = marf.as_analysis_db();
let analysis = db.execute(|db| {
type_check_version(&contract_identifier, &mut exprs, db, true, epoch, version)
});
if version < ClarityVersion::Clarity3 {
let err = analysis.unwrap_err();
assert_eq!(
CheckErrors::UndefinedVariable("stacks-block-height".to_string()),
err.err
);
} else {
assert!(analysis.is_ok());
}
// Initialize the contract
// Note that we're ignoring the analysis failure here so that we can test
// the runtime behavior. In Clarity 3, if this case somehow gets past the
// analysis, it should fail at runtime.
let result = owned_env.initialize_versioned_contract(
contract_identifier.clone(),
version,
contract,
None,
ASTRules::PrecheckSize,
);
let mut env = owned_env.get_exec_environment(None, None, &mut placeholder_context);
// Call the function
let eval_result = env.eval_read_only(&contract_identifier, "(test-func)");
// In Clarity 3, this should trigger a runtime error
if version < ClarityVersion::Clarity3 {
let err = eval_result.unwrap_err();
assert_eq!(
Error::Unchecked(CheckErrors::UndefinedVariable(
"stacks-block-height".to_string(),
)),
err
);
} else {
assert_eq!(Ok(Value::UInt(1)), eval_result);
}
}

View File

@@ -97,6 +97,7 @@ pub fn lookup_reserved_variable(
Ok(Some(sponsor))
}
NativeVariables::BlockHeight => {
// FIXME: this needs to be updated to epoch 3.0 vs epoch 2.x
runtime_cost(ClarityCostFunction::FetchVar, env, 1)?;
let block_height = env.global_context.database.get_current_block_height();
Ok(Some(Value::UInt(block_height as u128)))
@@ -136,6 +137,7 @@ pub fn lookup_reserved_variable(
}
NativeVariables::TenureHeight => {
runtime_cost(ClarityCostFunction::FetchVar, env, 1)?;
// FIXME: this is a placeholder and needs to be implemented correctly
let burn_block_height = env
.global_context
.database