Skip to content

Commit a86708f

Browse files
fix(platform): ensure document types only target valid tokens for token payments (#2631)
1 parent 662e131 commit a86708f

File tree

36 files changed

+834
-39
lines changed

36 files changed

+834
-39
lines changed

packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::data_contract::document_type::restricted_creation::CreationRestrictio
1212
#[cfg(feature = "validation")]
1313
use crate::data_contract::document_type::validator::StatelessJsonSchemaLazyValidator;
1414
use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements;
15+
use crate::data_contract::TokenContractPosition;
1516
use crate::document::transfer::Transferable;
1617
use crate::identity::SecurityLevel;
1718
use crate::nft::TradeMode;
@@ -625,6 +626,22 @@ impl DocumentTypeV1Getters for DocumentType {
625626
DocumentType::V1(v1) => v1.document_purchase_token_cost(),
626627
}
627628
}
629+
630+
fn all_document_token_costs(&self) -> Vec<&DocumentActionTokenCost> {
631+
match self {
632+
DocumentType::V0(_) => vec![],
633+
DocumentType::V1(v1) => v1.all_document_token_costs(),
634+
}
635+
}
636+
637+
fn all_external_token_costs_contract_tokens(
638+
&self,
639+
) -> BTreeMap<Identifier, BTreeSet<TokenContractPosition>> {
640+
match self {
641+
DocumentType::V0(_) => BTreeMap::new(),
642+
DocumentType::V1(v1) => v1.all_external_token_costs_contract_tokens(),
643+
}
644+
}
628645
}
629646

630647
impl DocumentTypeV1Getters for DocumentTypeRef<'_> {
@@ -669,6 +686,22 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> {
669686
DocumentTypeRef::V1(v1) => v1.document_purchase_token_cost(),
670687
}
671688
}
689+
690+
fn all_document_token_costs(&self) -> Vec<&DocumentActionTokenCost> {
691+
match self {
692+
DocumentTypeRef::V0(_) => vec![],
693+
DocumentTypeRef::V1(v1) => v1.all_document_token_costs(),
694+
}
695+
}
696+
697+
fn all_external_token_costs_contract_tokens(
698+
&self,
699+
) -> BTreeMap<Identifier, BTreeSet<TokenContractPosition>> {
700+
match self {
701+
DocumentTypeRef::V0(_) => BTreeMap::new(),
702+
DocumentTypeRef::V1(v1) => v1.all_external_token_costs_contract_tokens(),
703+
}
704+
}
672705
}
673706

674707
impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> {
@@ -713,4 +746,20 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> {
713746
DocumentTypeMutRef::V1(v1) => v1.document_purchase_token_cost(),
714747
}
715748
}
749+
750+
fn all_document_token_costs(&self) -> Vec<&DocumentActionTokenCost> {
751+
match self {
752+
DocumentTypeMutRef::V0(_) => vec![],
753+
DocumentTypeMutRef::V1(v1) => v1.all_document_token_costs(),
754+
}
755+
}
756+
757+
fn all_external_token_costs_contract_tokens(
758+
&self,
759+
) -> BTreeMap<Identifier, BTreeSet<TokenContractPosition>> {
760+
match self {
761+
DocumentTypeMutRef::V0(_) => BTreeMap::new(),
762+
DocumentTypeMutRef::V1(v1) => v1.all_external_token_costs_contract_tokens(),
763+
}
764+
}
716765
}

packages/rs-dpp/src/data_contract/document_type/accessors/v1/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
use crate::data_contract::TokenContractPosition;
12
use crate::tokens::token_amount_on_contract_token::DocumentActionTokenCost;
3+
use platform_value::Identifier;
4+
use std::collections::{BTreeMap, BTreeSet};
25

36
/// Trait providing getters for retrieving token costs associated with different document operations.
47
pub trait DocumentTypeV1Getters {
@@ -43,6 +46,15 @@ pub trait DocumentTypeV1Getters {
4346
/// - `Some(TokenActionCost)` if a purchase cost exists.
4447
/// - `None` if no cost is set for document purchase.
4548
fn document_purchase_token_cost(&self) -> Option<DocumentActionTokenCost>;
49+
50+
/// Returns all document token costs. This is generally used only in internal validation.
51+
fn all_document_token_costs(&self) -> Vec<&DocumentActionTokenCost>;
52+
53+
/// Returns the tokens used by external token costs as a set of token contract positions per contract.
54+
/// This is generally used only in internal validation.
55+
fn all_external_token_costs_contract_tokens(
56+
&self,
57+
) -> BTreeMap<Identifier, BTreeSet<TokenContractPosition>>;
4658
}
4759

4860
/// Trait providing setters for assigning token costs to different document operations.

packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/mod.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ mod v0;
22
mod v1;
33

44
use crate::data_contract::config::DataContractConfig;
5-
use crate::data_contract::document_type::v0::DocumentTypeV0;
65
use crate::data_contract::document_type::DocumentType;
7-
use crate::data_contract::DocumentName;
6+
use crate::data_contract::{DocumentName, TokenConfiguration, TokenContractPosition};
87
use crate::validation::operations::ProtocolValidationOperation;
98
use crate::version::PlatformVersion;
109
use crate::ProtocolError;
@@ -40,6 +39,7 @@ impl DocumentType {
4039
data_contract_id: Identifier,
4140
document_schemas: BTreeMap<DocumentName, Value>,
4241
schema_defs: Option<&BTreeMap<String, Value>>,
42+
token_configurations: &BTreeMap<TokenContractPosition, TokenConfiguration>,
4343
data_contact_config: &DataContractConfig,
4444
full_validation: bool,
4545
has_tokens: bool,
@@ -53,20 +53,22 @@ impl DocumentType {
5353
.class_method_versions
5454
.create_document_types_from_document_schemas
5555
{
56-
0 => DocumentTypeV0::create_document_types_from_document_schemas_v0(
56+
0 => DocumentType::create_document_types_from_document_schemas_v0(
5757
data_contract_id,
5858
document_schemas,
5959
schema_defs,
60+
token_configurations,
6061
data_contact_config,
6162
full_validation,
6263
validation_operations,
6364
platform_version,
6465
),
6566
// in v1 we add the ability to have contracts without documents and just tokens
66-
1 => DocumentTypeV0::create_document_types_from_document_schemas_v1(
67+
1 => DocumentType::create_document_types_from_document_schemas_v1(
6768
data_contract_id,
6869
document_schemas,
6970
schema_defs,
71+
token_configurations,
7072
data_contact_config,
7173
full_validation,
7274
has_tokens,

packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/v0/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
use crate::consensus::basic::data_contract::DocumentTypesAreMissingError;
22
use crate::data_contract::config::DataContractConfig;
33
use crate::data_contract::document_type::class_methods::consensus_or_protocol_data_contract_error;
4-
use crate::data_contract::document_type::v0::DocumentTypeV0;
54
use crate::data_contract::document_type::DocumentType;
6-
use crate::data_contract::DocumentName;
5+
use crate::data_contract::{DocumentName, TokenConfiguration, TokenContractPosition};
76
use crate::validation::operations::ProtocolValidationOperation;
87
use crate::version::PlatformVersion;
98
use crate::ProtocolError;
109
use platform_value::{Identifier, Value};
1110
use std::collections::BTreeMap;
1211

13-
impl DocumentTypeV0 {
12+
impl DocumentType {
13+
#[allow(clippy::too_many_arguments)]
1414
pub(in crate::data_contract) fn create_document_types_from_document_schemas_v0(
1515
data_contract_id: Identifier,
1616
document_schemas: BTreeMap<DocumentName, Value>,
1717
schema_defs: Option<&BTreeMap<String, Value>>,
18+
token_configurations: &BTreeMap<TokenContractPosition, TokenConfiguration>,
1819
data_contact_config: &DataContractConfig,
1920
full_validation: bool,
2021
validation_operations: &mut Vec<ProtocolValidationOperation>,
@@ -40,6 +41,7 @@ impl DocumentTypeV0 {
4041
&name,
4142
schema,
4243
schema_defs,
44+
token_configurations,
4345
data_contact_config,
4446
full_validation,
4547
validation_operations,
@@ -84,6 +86,7 @@ mod tests {
8486
id,
8587
Default::default(),
8688
None,
89+
&BTreeMap::new(),
8790
&config,
8891
false,
8992
false,

packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/v1/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
use crate::consensus::basic::data_contract::DocumentTypesAreMissingError;
22
use crate::data_contract::config::DataContractConfig;
33
use crate::data_contract::document_type::class_methods::consensus_or_protocol_data_contract_error;
4-
use crate::data_contract::document_type::v0::DocumentTypeV0;
54
use crate::data_contract::document_type::DocumentType;
6-
use crate::data_contract::DocumentName;
5+
use crate::data_contract::{DocumentName, TokenConfiguration, TokenContractPosition};
76
use crate::validation::operations::ProtocolValidationOperation;
87
use crate::version::PlatformVersion;
98
use crate::ProtocolError;
109
use platform_value::{Identifier, Value};
1110
use std::collections::BTreeMap;
1211

13-
impl DocumentTypeV0 {
12+
impl DocumentType {
1413
#[allow(clippy::too_many_arguments)]
1514
pub(in crate::data_contract) fn create_document_types_from_document_schemas_v1(
1615
data_contract_id: Identifier,
1716
document_schemas: BTreeMap<DocumentName, Value>,
1817
schema_defs: Option<&BTreeMap<String, Value>>,
18+
token_configurations: &BTreeMap<TokenContractPosition, TokenConfiguration>,
1919
data_contact_config: &DataContractConfig,
2020
full_validation: bool,
2121
has_tokens: bool,
@@ -42,6 +42,7 @@ impl DocumentTypeV0 {
4242
&name,
4343
schema,
4444
schema_defs,
45+
token_configurations,
4546
data_contact_config,
4647
full_validation,
4748
validation_operations,

packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::data_contract::document_type::{
55
property_names, DocumentProperty, DocumentPropertyType, DocumentType,
66
};
77
use crate::data_contract::errors::DataContractError;
8+
use crate::data_contract::{TokenConfiguration, TokenContractPosition};
89
use crate::util::json_schema::resolve_uri;
910
use crate::validation::operations::ProtocolValidationOperation;
1011
use crate::ProtocolError;
@@ -42,6 +43,7 @@ impl DocumentType {
4243
name: &str,
4344
schema: Value,
4445
schema_defs: Option<&BTreeMap<String, Value>>,
46+
token_configurations: &BTreeMap<TokenContractPosition, TokenConfiguration>,
4547
data_contact_config: &DataContractConfig,
4648
full_validation: bool,
4749
validation_operations: &mut Vec<ProtocolValidationOperation>,
@@ -70,6 +72,7 @@ impl DocumentType {
7072
name,
7173
schema,
7274
schema_defs,
75+
token_configurations,
7376
data_contact_config,
7477
full_validation,
7578
validation_operations,

packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ use crate::consensus::basic::data_contract::ContestedUniqueIndexOnMutableDocumen
2828
use crate::consensus::basic::data_contract::ContestedUniqueIndexWithUniqueIndexError;
2929
#[cfg(any(test, feature = "validation"))]
3030
use crate::consensus::basic::data_contract::InvalidDocumentTypeNameError;
31+
use crate::consensus::basic::data_contract::RedundantDocumentPaidForByTokenWithContractId;
3132
#[cfg(feature = "validation")]
3233
use crate::consensus::basic::data_contract::TokenPaymentByBurningOnlyAllowedOnInternalTokenError;
3334
#[cfg(feature = "validation")]
3435
use crate::consensus::basic::document::MissingPositionsInDocumentTypePropertiesError;
36+
use crate::consensus::basic::token::InvalidTokenPositionError;
3537
#[cfg(feature = "validation")]
3638
use crate::consensus::basic::BasicError;
3739
use crate::data_contract::config::v0::DataContractConfigGettersV0;
@@ -56,7 +58,7 @@ use crate::data_contract::document_type::v1::DocumentTypeV1;
5658
use crate::data_contract::document_type::{property_names, DocumentType};
5759
use crate::data_contract::errors::DataContractError;
5860
use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements;
59-
use crate::data_contract::TokenContractPosition;
61+
use crate::data_contract::{TokenConfiguration, TokenContractPosition};
6062
use crate::identity::SecurityLevel;
6163
use crate::tokens::gas_fees_paid_by::GasFeesPaidBy;
6264
use crate::tokens::token_amount_on_contract_token::{
@@ -78,6 +80,7 @@ impl DocumentTypeV1 {
7880
name: &str,
7981
schema: Value,
8082
schema_defs: Option<&BTreeMap<String, Value>>,
83+
token_configurations: &BTreeMap<TokenContractPosition, TokenConfiguration>,
8184
data_contact_config: &DataContractConfig,
8285
full_validation: bool, // we don't need to validate if loaded from state
8386
validation_operations: &mut Vec<ProtocolValidationOperation>,
@@ -562,7 +565,7 @@ impl DocumentTypeV1 {
562565
.transpose()?
563566
.map(|action_cost| {
564567
// Extract an optional contract_id. Adjust the key if necessary.
565-
let contract_id = action_cost.get_optional_identifier("contractId")?;
568+
let target_contract_id = action_cost.get_optional_identifier("contractId")?;
566569
// Extract token_contract_position as an integer, then convert it.
567570
let token_contract_position =
568571
action_cost.get_integer::<TokenContractPosition>("tokenPosition")?;
@@ -577,15 +580,38 @@ impl DocumentTypeV1 {
577580

578581
#[cfg(feature = "validation")]
579582
if full_validation {
583+
// contract id is none if we are on our own contract
584+
if target_contract_id.is_none() && !token_configurations.contains_key(&token_contract_position) {
585+
return Err(ProtocolError::ConsensusError(
586+
ConsensusError::BasicError(
587+
BasicError::InvalidTokenPositionError(
588+
InvalidTokenPositionError::new(
589+
token_configurations.last_key_value().map(|(position, _)| *position),
590+
token_contract_position,
591+
),
592+
),
593+
)
594+
.into(),
595+
));
596+
}
580597

581598
// If contractId is present and user tries to burn, bail out:
582-
if let Some(contract_id) = contract_id {
599+
if let Some(target_contract_id) = target_contract_id {
600+
if target_contract_id == data_contract_id {
601+
// we are in the same contract, but we set the data contract id
602+
return Err(ProtocolError::ConsensusError(
603+
ConsensusError::BasicError(
604+
BasicError::RedundantDocumentPaidForByTokenWithContractId(RedundantDocumentPaidForByTokenWithContractId::new(target_contract_id))
605+
)
606+
.into(),
607+
));
608+
}
583609
if effect == DocumentActionTokenEffect::BurnToken {
584610
return Err(ProtocolError::ConsensusError(
585611
ConsensusError::BasicError(
586612
BasicError::TokenPaymentByBurningOnlyAllowedOnInternalTokenError(
587613
TokenPaymentByBurningOnlyAllowedOnInternalTokenError::new(
588-
contract_id,
614+
target_contract_id,
589615
token_contract_position,
590616
key.to_string(),
591617
),
@@ -605,7 +631,7 @@ impl DocumentTypeV1 {
605631
.unwrap_or(GasFeesPaidBy::DocumentOwner);
606632

607633
Ok(DocumentActionTokenCost {
608-
contract_id,
634+
contract_id: target_contract_id,
609635
token_contract_position,
610636
token_amount,
611637
effect,
@@ -686,6 +712,7 @@ mod tests {
686712
"valid_name-a-b-123",
687713
schema,
688714
None,
715+
&BTreeMap::new(),
689716
&config,
690717
true,
691718
&mut vec![],
@@ -717,6 +744,7 @@ mod tests {
717744
"",
718745
schema,
719746
None,
747+
&BTreeMap::new(),
720748
&config,
721749
true,
722750
&mut vec![],
@@ -759,6 +787,7 @@ mod tests {
759787
&"a".repeat(65),
760788
schema,
761789
None,
790+
&BTreeMap::new(),
762791
&config,
763792
true,
764793
&mut vec![],
@@ -827,6 +856,7 @@ mod tests {
827856
"invalid&name",
828857
schema,
829858
None,
859+
&BTreeMap::new(),
830860
&config,
831861
true,
832862
&mut vec![],

0 commit comments

Comments
 (0)