use stacks_common::address::{
C32_ADDRESS_VERSION_MAINNET_MULTISIG, C32_ADDRESS_VERSION_MAINNET_SINGLESIG,
C32_ADDRESS_VERSION_TESTNET_MULTISIG, C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
};
use stacks_common::util::hash::hex_bytes;
use crate::vm::contexts::GlobalContext;
use crate::vm::costs::cost_functions::ClarityCostFunction;
use crate::vm::costs::{cost_functions, runtime_cost, CostTracker};
use crate::vm::errors::{
check_argument_count, check_arguments_at_least, check_arguments_at_most, CheckErrors, Error,
InterpreterError, InterpreterResult as Result, RuntimeErrorType,
};
use crate::vm::representations::{
ClarityName, SymbolicExpression, CONTRACT_MAX_NAME_LENGTH, CONTRACT_MIN_NAME_LENGTH,
};
use crate::vm::types::signatures::{BUFF_1, BUFF_20};
use crate::vm::types::{
ASCIIData, BuffData, BufferLength, CharType, OptionalData, PrincipalData,
QualifiedContractIdentifier, ResponseData, SequenceData, SequenceSubtype,
StandardPrincipalData, TupleData, TypeSignature, Value,
};
use crate::vm::{eval, ContractName, Environment, LocalContext};
pub enum PrincipalConstructErrorCode {
VERSION_BYTE = 0,
BUFFER_LENGTH = 1,
CONTRACT_NAME = 2,
}
fn version_matches_mainnet(version: u8) -> bool {
version == C32_ADDRESS_VERSION_MAINNET_MULTISIG
|| version == C32_ADDRESS_VERSION_MAINNET_SINGLESIG
}
fn version_matches_testnet(version: u8) -> bool {
version == C32_ADDRESS_VERSION_TESTNET_MULTISIG
|| version == C32_ADDRESS_VERSION_TESTNET_SINGLESIG
}
fn version_matches_current_network(version: u8, global_context: &GlobalContext) -> bool {
let context_is_mainnet = global_context.mainnet;
let context_is_testnet = !global_context.mainnet;
(version_matches_mainnet(version) && context_is_mainnet)
|| (version_matches_testnet(version) && context_is_testnet)
}
pub fn special_is_standard(
args: &[SymbolicExpression],
env: &mut Environment,
context: &LocalContext,
) -> Result<Value> {
check_argument_count(1, args)?;
runtime_cost(ClarityCostFunction::IsStandard, env, 0)?;
let owner = eval(&args[0], env, context)?;
let version = match owner {
Value::Principal(PrincipalData::Standard(StandardPrincipalData(version, _bytes))) => {
version
}
Value::Principal(PrincipalData::Contract(QualifiedContractIdentifier {
issuer,
name: _,
})) => issuer.0,
_ => return Err(CheckErrors::TypeValueError(TypeSignature::PrincipalType, owner).into()),
};
Ok(Value::Bool(version_matches_current_network(
version,
env.global_context,
)))
}
fn create_principal_destruct_tuple(
version: u8,
hash_bytes: &[u8; 20],
name_opt: Option<ContractName>,
) -> Result<Value> {
Ok(Value::Tuple(
TupleData::from_data(vec![
(
"version".into(),
Value::Sequence(SequenceData::Buffer(BuffData {
data: vec![version],
})),
),
(
"hash-bytes".into(),
Value::Sequence(SequenceData::Buffer(BuffData {
data: hash_bytes.to_vec(),
})),
),
(
"name".into(),
Value::Optional(OptionalData {
data: name_opt.map(|name| Box::new(Value::from(ASCIIData::from(name)))),
}),
),
])
.map_err(|_| InterpreterError::Expect("FAIL: Failed to initialize tuple.".into()))?,
))
}
fn create_principal_true_error_response(error_int: PrincipalConstructErrorCode) -> Result<Value> {
Value::error(Value::Tuple(
TupleData::from_data(vec![
("error_code".into(), Value::UInt(error_int as u128)),
("value".into(), Value::none()),
])
.map_err(|_| InterpreterError::Expect("FAIL: Failed to initialize tuple.".into()))?,
))
.map_err(|_| {
InterpreterError::Expect("FAIL: Failed to initialize (err ..) response".into()).into()
})
}
fn create_principal_value_error_response(
error_int: PrincipalConstructErrorCode,
value: Value,
) -> Result<Value> {
Value::error(Value::Tuple(
TupleData::from_data(vec![
("error_code".into(), Value::UInt(error_int as u128)),
(
"value".into(),
Value::some(value).map_err(|_| {
InterpreterError::Expect("Unexpected problem creating Value.".into())
})?,
),
])
.map_err(|_| InterpreterError::Expect("FAIL: Failed to initialize tuple.".into()))?,
))
.map_err(|_| {
InterpreterError::Expect("FAIL: Failed to initialize (err ..) response".into()).into()
})
}
pub fn special_principal_destruct(
args: &[SymbolicExpression],
env: &mut Environment,
context: &LocalContext,
) -> Result<Value> {
check_argument_count(1, args)?;
runtime_cost(ClarityCostFunction::PrincipalDestruct, env, 0)?;
let principal = eval(&args[0], env, context)?;
let (version_byte, hash_bytes, name_opt) = match principal {
Value::Principal(PrincipalData::Standard(StandardPrincipalData(version, bytes))) => {
(version, bytes, None)
}
Value::Principal(PrincipalData::Contract(QualifiedContractIdentifier { issuer, name })) => {
(issuer.0, issuer.1, Some(name))
}
_ => {
return Err(CheckErrors::TypeValueError(TypeSignature::PrincipalType, principal).into())
}
};
let version_byte_is_valid = version_matches_current_network(version_byte, env.global_context);
let tuple = create_principal_destruct_tuple(version_byte, &hash_bytes, name_opt)?;
Ok(Value::Response(ResponseData {
committed: version_byte_is_valid,
data: Box::new(tuple),
}))
}
pub fn special_principal_construct(
args: &[SymbolicExpression],
env: &mut Environment,
context: &LocalContext,
) -> Result<Value> {
check_arguments_at_least(2, args)?;
check_arguments_at_most(3, args)?;
runtime_cost(ClarityCostFunction::PrincipalConstruct, env, 0)?;
let version = eval(&args[0], env, context)?;
let hash_bytes = eval(&args[1], env, context)?;
let name_opt = if args.len() > 2 {
Some(eval(&args[2], env, context)?)
} else {
None
};
let verified_version = match version {
Value::Sequence(SequenceData::Buffer(BuffData { ref data })) => data,
_ => {
return {
Err(CheckErrors::TypeValueError(BUFF_1.clone(), version).into())
};
}
};
let version_byte = if verified_version.len() > 1 {
return Err(CheckErrors::TypeValueError(BUFF_1.clone(), version).into());
} else if verified_version.is_empty() {
return create_principal_true_error_response(PrincipalConstructErrorCode::BUFFER_LENGTH);
} else {
(*verified_version)[0]
};
if version_byte >= 32 {
return create_principal_true_error_response(PrincipalConstructErrorCode::BUFFER_LENGTH);
}
let version_byte_is_valid = version_matches_current_network(version_byte, env.global_context);
let verified_hash_bytes = match hash_bytes {
Value::Sequence(SequenceData::Buffer(BuffData { ref data })) => data,
_ => return Err(CheckErrors::TypeValueError(BUFF_20.clone(), hash_bytes).into()),
};
if verified_hash_bytes.len() > 20 {
return Err(CheckErrors::TypeValueError(BUFF_20.clone(), hash_bytes).into());
}
if verified_hash_bytes.len() < 20 {
return create_principal_true_error_response(PrincipalConstructErrorCode::BUFFER_LENGTH);
}
let mut transfer_buffer = [0u8; 20];
transfer_buffer.copy_from_slice(verified_hash_bytes);
let principal_data = StandardPrincipalData(version_byte, transfer_buffer);
let principal = if let Some(name) = name_opt {
let name_bytes = match name {
Value::Sequence(SequenceData::String(CharType::ASCII(ascii_data))) => ascii_data,
_ => {
return Err(CheckErrors::TypeValueError(
TypeSignature::contract_name_string_ascii_type()?,
name,
)
.into())
}
};
if name_bytes.data.len() < CONTRACT_MIN_NAME_LENGTH {
return create_principal_true_error_response(
PrincipalConstructErrorCode::CONTRACT_NAME,
);
}
if name_bytes.data.len() > CONTRACT_MAX_NAME_LENGTH {
return Err(CheckErrors::TypeValueError(
TypeSignature::contract_name_string_ascii_type()?,
Value::from(name_bytes),
)
.into());
}
let name_string = String::from_utf8(name_bytes.data).map_err(|_| {
InterpreterError::Expect(
"FAIL: could not convert bytes of type (string-ascii 40) back to a UTF-8 string"
.into(),
)
})?;
let contract_name = match ContractName::try_from(name_string) {
Ok(cn) => cn,
Err(_) => {
return create_principal_true_error_response(
PrincipalConstructErrorCode::CONTRACT_NAME,
);
}
};
Value::Principal(PrincipalData::Contract(QualifiedContractIdentifier::new(
principal_data,
contract_name,
)))
} else {
Value::Principal(PrincipalData::Standard(principal_data))
};
if version_byte_is_valid {
Ok(Value::okay(principal).map_err(|_| {
InterpreterError::Expect("FAIL: failed to build an (ok ..) response".into())
})?)
} else {
create_principal_value_error_response(PrincipalConstructErrorCode::VERSION_BYTE, principal)
}
}