diff --git a/CHANGELOG.md b/CHANGELOG.md index 066e6d9a66..997c64f4ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE - Creates epoch 3.3 and costs-4 in preparation for a hardfork to activate Clarity 4 - Adds support for new Clarity 4 builtins (not activated until epoch 3.3): - `contract-hash?` + - `current-contract` - `block-time` - `to-ascii?` - Added `contract_cost_limit_percentage` to the miner config file — sets the percentage of a block’s execution cost at which, if a large non-boot contract call would cause a BlockTooBigError, the miner will stop adding further non-boot contract calls and only include STX transfers and boot contract calls for the remainder of the block. diff --git a/clarity/src/vm/analysis/arithmetic_checker/mod.rs b/clarity/src/vm/analysis/arithmetic_checker/mod.rs index c8dfeeeba6..83be68ab23 100644 --- a/clarity/src/vm/analysis/arithmetic_checker/mod.rs +++ b/clarity/src/vm/analysis/arithmetic_checker/mod.rs @@ -143,7 +143,7 @@ impl ArithmeticOnlyChecker<'_> { match native_var { ContractCaller | TxSender | TotalLiquidMicroSTX | BlockHeight | BurnBlockHeight | Regtest | TxSponsor | Mainnet | ChainId | StacksBlockHeight | TenureHeight - | BlockTime => Err(Error::VariableForbidden(native_var)), + | BlockTime | CurrentContract => Err(Error::VariableForbidden(native_var)), NativeNone | NativeTrue | NativeFalse => Ok(()), } } else { 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 1a6f517724..31cf5140dd 100644 --- a/clarity/src/vm/analysis/type_checker/v2_05/mod.rs +++ b/clarity/src/vm/analysis/type_checker/v2_05/mod.rs @@ -330,9 +330,9 @@ fn type_reserved_variable(variable_name: &str) -> Result, NativeFalse => TypeSignature::BoolType, TotalLiquidMicroSTX => TypeSignature::UIntType, Regtest => TypeSignature::BoolType, - TxSponsor | Mainnet | ChainId | StacksBlockHeight | TenureHeight | BlockTime => { + TxSponsor | Mainnet | ChainId | StacksBlockHeight | TenureHeight | BlockTime | CurrentContract => { return Err(CheckErrors::Expects( - "tx-sponsor, mainnet, chain-id, stacks-block-height, tenure-height, and block-time should not reach here in 2.05".into(), + "tx-sponsor, mainnet, chain-id, stacks-block-height, tenure-height, block-time, and current-contract should not reach here in 2.05".into(), ) .into()) } 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 ae794cdbbc..131186958c 100644 --- a/clarity/src/vm/analysis/type_checker/v2_1/mod.rs +++ b/clarity/src/vm/analysis/type_checker/v2_1/mod.rs @@ -1022,6 +1022,7 @@ fn type_reserved_variable( Regtest => TypeSignature::BoolType, Mainnet => TypeSignature::BoolType, ChainId => TypeSignature::UIntType, + CurrentContract => TypeSignature::PrincipalType, BlockTime => TypeSignature::UIntType, }; Ok(Some(var_type)) diff --git a/clarity/src/vm/docs/mod.rs b/clarity/src/vm/docs/mod.rs index db2de9fb13..68e995bd7d 100644 --- a/clarity/src/vm/docs/mod.rs +++ b/clarity/src/vm/docs/mod.rs @@ -125,6 +125,15 @@ to the same contract principal.", example: "(print contract-caller) ;; Will print out a Stacks address of the transaction sender", }; +const CURRENT_CONTRACT_KEYWORD: SimpleKeywordAPI = SimpleKeywordAPI { + name: "current-contract", + snippet: "current-contract", + output_type: "principal", + description: "Returns the principal of the current contract.", + example: + "(print current-contract) ;; Will print out the Stacks address of the current contract", +}; + const STACKS_BLOCK_HEIGHT_KEYWORD: SimpleKeywordAPI = SimpleKeywordAPI { name: "stacks-block-height", snippet: "stacks-block-height", @@ -2690,6 +2699,7 @@ pub fn make_keyword_reference(variable: &NativeVariables) -> Option NativeVariables::Mainnet => MAINNET_KEYWORD.clone(), NativeVariables::ChainId => CHAINID_KEYWORD.clone(), NativeVariables::TxSponsor => TX_SPONSOR_KEYWORD.clone(), + NativeVariables::CurrentContract => CURRENT_CONTRACT_KEYWORD.clone(), NativeVariables::BlockTime => BLOCK_TIME_KEYWORD.clone(), }; Some(KeywordAPI { diff --git a/clarity/src/vm/tests/variables.rs b/clarity/src/vm/tests/variables.rs index bb23fcb82d..f1a0f16e13 100644 --- a/clarity/src/vm/tests/variables.rs +++ b/clarity/src/vm/tests/variables.rs @@ -27,7 +27,7 @@ use crate::vm::{ database::MemoryBackingStore, errors::{CheckErrors, Error}, tests::{tl_env_factory, TopLevelMemoryEnvironmentGenerator}, - types::{QualifiedContractIdentifier, Value}, + types::{PrincipalData, QualifiedContractIdentifier, Value}, ClarityVersion, ContractContext, }; @@ -849,8 +849,10 @@ fn reuse_stacks_block_height( ); } -#[apply(test_clarity_versions)] -fn reuse_tenure_height( +#[cfg(test)] +fn reuse_builtin_name( + name: &str, + version_check: fn(ClarityVersion, StacksEpochId) -> bool, version: ClarityVersion, epoch: StacksEpochId, mut tl_env_factory: TopLevelMemoryEnvironmentGenerator, @@ -861,16 +863,18 @@ fn reuse_tenure_height( epoch, &mut tl_env_factory, "data-var", - r#" - (define-data-var tenure-height uint u1234) + &format!( + r#" + (define-data-var {name} uint u1234) (define-read-only (test-func) - (var-get tenure-height) + (var-get {name}) ) - "#, + "# + ), &[( WhenError::Initialization, - |version, _| version >= ClarityVersion::Clarity3, - CheckErrors::NameAlreadyUsed("tenure-height".to_string()), + version_check, + CheckErrors::NameAlreadyUsed(name.to_string()), )], Value::UInt(1234), ); @@ -881,16 +885,18 @@ fn reuse_tenure_height( epoch, &mut tl_env_factory, "map", - r#" - (define-map tenure-height uint uint) + &format!( + r#" + (define-map {name} uint uint) (define-private (test-func) - (map-insert tenure-height u1 u2) + (map-insert {name} u1 u2) ) - "#, + "# + ), &[( WhenError::Initialization, - |version, _| version >= ClarityVersion::Clarity3, - CheckErrors::NameAlreadyUsed("tenure-height".to_string()), + version_check, + CheckErrors::NameAlreadyUsed(name.to_string()), )], Value::Bool(true), ); @@ -901,17 +907,19 @@ fn reuse_tenure_height( epoch, &mut tl_env_factory, "let", - r#" + &format!( + r#" (define-private (test-func) - (let ((tenure-height 32)) - tenure-height + (let (({name} 32)) + {name} ) ) - "#, + "# + ), &[( WhenError::Runtime, - |version, _| version >= ClarityVersion::Clarity3, - CheckErrors::NameAlreadyUsed("tenure-height".to_string()), + version_check, + CheckErrors::NameAlreadyUsed(name.to_string()), )], Value::Int(32), ); @@ -922,20 +930,22 @@ fn reuse_tenure_height( epoch, &mut tl_env_factory, "match-binding", - r#" + &format!( + r#" (define-read-only (test-func) (let ((x (if true (ok u5) (err u7)))) (match x - tenure-height 3 + {name} 3 e 4 ) ) ) - "#, + "# + ), &[( WhenError::Runtime, - |version, _| version >= ClarityVersion::Clarity3, - CheckErrors::NameAlreadyUsed("tenure-height".to_string()), + version_check, + CheckErrors::NameAlreadyUsed(name.to_string()), )], Value::Int(3), ); @@ -946,14 +956,16 @@ fn reuse_tenure_height( epoch, &mut tl_env_factory, "function", - r#" - (define-private (tenure-height) true) - (define-private (test-func) (tenure-height)) - "#, + &format!( + r#" + (define-private ({name}) true) + (define-private (test-func) ({name})) + "# + ), &[( WhenError::Initialization, - |version, _| version >= ClarityVersion::Clarity3, - CheckErrors::NameAlreadyUsed("tenure-height".to_string()), + version_check, + CheckErrors::NameAlreadyUsed(name.to_string()), )], Value::Bool(true), ); @@ -964,14 +976,16 @@ fn reuse_tenure_height( epoch, &mut tl_env_factory, "constant", - r#" - (define-constant tenure-height u1234) - (define-read-only (test-func) tenure-height) - "#, + &format!( + r#" + (define-constant {name} u1234) + (define-read-only (test-func) {name}) + "# + ), &[( WhenError::Initialization, - |version, _| version >= ClarityVersion::Clarity3, - CheckErrors::NameAlreadyUsed("tenure-height".to_string()), + version_check, + CheckErrors::NameAlreadyUsed(name.to_string()), )], Value::UInt(1234), ); @@ -982,14 +996,16 @@ fn reuse_tenure_height( epoch, &mut tl_env_factory, "trait", - r#" - (define-trait tenure-height ()) + &format!( + r#" + (define-trait {name} ()) (define-read-only (test-func) false) - "#, + "# + ), &[( WhenError::Initialization, - |version, _| version >= ClarityVersion::Clarity3, - CheckErrors::NameAlreadyUsed("tenure-height".to_string()), + version_check, + CheckErrors::NameAlreadyUsed(name.to_string()), )], Value::Bool(false), ); @@ -1000,11 +1016,13 @@ fn reuse_tenure_height( epoch, &mut tl_env_factory, "tuple", - r#" + &format!( + r#" (define-read-only (test-func) - (get tenure-height { tenure-height: 1234 }) + (get {name} {{ {name}: 1234 }}) ) - "#, + "# + ), &[], Value::Int(1234), ); @@ -1015,14 +1033,16 @@ fn reuse_tenure_height( epoch, &mut tl_env_factory, "trait", - r#" - (define-fungible-token tenure-height) + &format!( + r#" + (define-fungible-token {name}) (define-read-only (test-func) false) - "#, + "# + ), &[( WhenError::Initialization, - |version, _| version >= ClarityVersion::Clarity3, - CheckErrors::NameAlreadyUsed("tenure-height".to_string()), + version_check, + CheckErrors::NameAlreadyUsed(name.to_string()), )], Value::Bool(false), ); @@ -1033,14 +1053,16 @@ fn reuse_tenure_height( epoch, &mut tl_env_factory, "trait", - r#" - (define-non-fungible-token tenure-height uint) + &format!( + r#" + (define-non-fungible-token {name} uint) (define-read-only (test-func) false) - "#, + "# + ), &[( WhenError::Initialization, - |version, _| version >= ClarityVersion::Clarity3, - CheckErrors::NameAlreadyUsed("tenure-height".to_string()), + version_check, + CheckErrors::NameAlreadyUsed(name.to_string()), )], Value::Bool(false), ); @@ -1051,14 +1073,16 @@ fn reuse_tenure_height( epoch, &mut tl_env_factory, "function", - r#" - (define-public (tenure-height) (ok true)) - (define-private (test-func) (unwrap-panic (tenure-height))) - "#, + &format!( + r#" + (define-public ({name}) (ok true)) + (define-private (test-func) (unwrap-panic ({name}))) + "# + ), &[( WhenError::Initialization, - |version, _| version >= ClarityVersion::Clarity3, - CheckErrors::NameAlreadyUsed("tenure-height".to_string()), + version_check, + CheckErrors::NameAlreadyUsed(name.to_string()), )], Value::Bool(true), ); @@ -1069,14 +1093,16 @@ fn reuse_tenure_height( epoch, &mut tl_env_factory, "function", - r#" - (define-read-only (tenure-height) true) - (define-private (test-func) (tenure-height)) - "#, + &format!( + r#" + (define-read-only ({name}) true) + (define-private (test-func) ({name})) + "# + ), &[( WhenError::Initialization, - |version, _| version >= ClarityVersion::Clarity3, - CheckErrors::NameAlreadyUsed("tenure-height".to_string()), + version_check, + CheckErrors::NameAlreadyUsed(name.to_string()), )], Value::Bool(true), ); @@ -1192,3 +1218,105 @@ fn test_block_time_in_expressions() { info!("time-in-response result: {:?}", eval_result); assert_eq!(Ok(Value::okay(Value::UInt(1)).unwrap()), eval_result); } + +#[apply(test_clarity_versions)] +fn reuse_tenure_height( + version: ClarityVersion, + epoch: StacksEpochId, + tl_env_factory: TopLevelMemoryEnvironmentGenerator, +) { + fn version_check(version: ClarityVersion, _epoch: StacksEpochId) -> bool { + version >= ClarityVersion::Clarity3 + } + reuse_builtin_name( + "tenure-height", + version_check, + version, + epoch, + tl_env_factory, + ); +} + +#[apply(test_clarity_versions)] +fn test_current_contract( + version: ClarityVersion, + epoch: StacksEpochId, + mut tl_env_factory: TopLevelMemoryEnvironmentGenerator, +) { + let contract = "(define-read-only (test-func) current-contract)"; + + let 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::Clarity4 { + let err = analysis.unwrap_err(); + assert_eq!( + CheckErrors::UndefinedVariable("current-contract".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, &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::Clarity4 { + let err = eval_result.unwrap_err(); + assert_eq!( + Error::Unchecked(CheckErrors::UndefinedVariable( + "current-contract".to_string(), + )), + err + ); + } else { + assert_eq!( + Ok(Value::Principal(PrincipalData::Contract( + contract_identifier + ))), + eval_result + ); + } +} + +/// Test the checks on reuse of the `current-contract` name +#[apply(test_clarity_versions)] +fn reuse_current_contract( + version: ClarityVersion, + epoch: StacksEpochId, + tl_env_factory: TopLevelMemoryEnvironmentGenerator, +) { + fn version_check(version: ClarityVersion, _epoch: StacksEpochId) -> bool { + version >= ClarityVersion::Clarity4 + } + reuse_builtin_name( + "current-contract", + version_check, + version, + epoch, + tl_env_factory, + ); +} diff --git a/clarity/src/vm/variables.rs b/clarity/src/vm/variables.rs index 3f11d6e4b9..cb84336dc9 100644 --- a/clarity/src/vm/variables.rs +++ b/clarity/src/vm/variables.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use clarity_types::types::PrincipalData; use stacks_common::types::StacksEpochId; use super::errors::InterpreterError; @@ -39,7 +40,8 @@ define_versioned_named_enum_with_max!(NativeVariables(ClarityVersion) { ChainId("chain-id", ClarityVersion::Clarity2, None), StacksBlockHeight("stacks-block-height", ClarityVersion::Clarity3, None), TenureHeight("tenure-height", ClarityVersion::Clarity3, None), - BlockTime("block-time", ClarityVersion::Clarity4, None) + BlockTime("block-time", ClarityVersion::Clarity4, None), + CurrentContract("current-contract", ClarityVersion::Clarity4, None) }); pub fn is_reserved_name(name: &str, version: &ClarityVersion) -> bool { @@ -134,6 +136,10 @@ pub fn lookup_reserved_variable( let tenure_height = env.global_context.database.get_tenure_height()?; Ok(Some(Value::UInt(tenure_height as u128))) } + NativeVariables::CurrentContract => { + let contract = env.contract_context.contract_identifier.clone(); + Ok(Some(Value::Principal(PrincipalData::Contract(contract)))) + } NativeVariables::BlockTime => { runtime_cost(ClarityCostFunction::FetchVar, env, 1)?; let block_time = env.global_context.database.get_current_block_time()?;