diff --git a/packages/platform-test-suite/test/e2e/dpns.spec.js b/packages/platform-test-suite/test/e2e/dpns.spec.js index e8cc3218a48..f5146dfc554 100644 --- a/packages/platform-test-suite/test/e2e/dpns.spec.js +++ b/packages/platform-test-suite/test/e2e/dpns.spec.js @@ -67,7 +67,7 @@ describe('DPNS', () => { identity = await ownerClient.platform.identities.get(dpnsOwnerId); expect(identity).to.exist(); - await ownerClient.platform.identities.topUp(dpnsOwnerId, 500); + await ownerClient.platform.identities.topUp(dpnsOwnerId, 1000); }); after(async () => { diff --git a/packages/platform-test-suite/test/functional/platform/Identity.spec.js b/packages/platform-test-suite/test/functional/platform/Identity.spec.js index 3a1bf18e5af..d3c4c4378ff 100644 --- a/packages/platform-test-suite/test/functional/platform/Identity.spec.js +++ b/packages/platform-test-suite/test/functional/platform/Identity.spec.js @@ -22,6 +22,7 @@ const { Identifier, IdentityPublicKey, InvalidInstantAssetLockProofSignatureError, + InvalidAssetLockProofValueError, IdentityAssetLockTransactionOutPointAlreadyExistsError, BasicECDSAError, IdentityPublicKeyWithWitness, @@ -55,6 +56,22 @@ describe('Platform', () => { expect(identity).to.exist(); }); + it('should fail to create an identity if asset lock amount is less than minimal', async () => { + let broadcastError; + + try { + await client.platform.identities.register(117000); + } catch (e) { + broadcastError = e; + } + + expect(broadcastError).to.be.an.instanceOf(StateTransitionBroadcastError); + expect(broadcastError.getCause().getCode()).to.equal(4028); + expect(broadcastError.getCause()).to.be.an.instanceOf( + InvalidAssetLockProofValueError, + ); + }); + it('should fail to create an identity if instantLock is not valid', async () => { await client.platform.initialize(); @@ -440,7 +457,7 @@ describe('Platform', () => { transaction, privateKey, outputIndex, - } = await client.platform.identities.utils.createAssetLockTransaction(1); + } = await client.platform.identities.utils.createAssetLockTransaction(1000); const account = await client.getWalletAccount(); diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index ea032c311bc..16ba3bd8622 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -177,6 +177,7 @@ impl ErrorWithCode for StateError { Self::MissingIdentityPublicKeyIdsError { .. } => 4024, Self::IdentityInsufficientBalanceError(_) => 4026, Self::IdentityPublicKeyAlreadyExistsForUniqueContractBoundsError(_) => 4027, + Self::InvalidAssetLockProofValueError(_) => 4028, } } } diff --git a/packages/rs-dpp/src/errors/consensus/state/identity/invalid_asset_lock_proof_value.rs b/packages/rs-dpp/src/errors/consensus/state/identity/invalid_asset_lock_proof_value.rs new file mode 100644 index 00000000000..9e4326620b3 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/identity/invalid_asset_lock_proof_value.rs @@ -0,0 +1,43 @@ +use crate::errors::ProtocolError; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +use crate::consensus::ConsensusError; + +use crate::consensus::state::state_error::StateError; +use bincode::{Decode, Encode}; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("Asset lock value {value:?} must be higher than minimal value of {min_value:?}")] +#[platform_serialize(unversioned)] +pub struct InvalidAssetLockProofValueError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + value: u64, + min_value: u64, +} + +impl InvalidAssetLockProofValueError { + pub fn new(value: u64, min_value: u64) -> Self { + Self { value, min_value } + } + + pub fn value(&self) -> u64 { + self.value + } + + pub fn min_value(&self) -> u64 { + self.min_value + } +} + +impl From for ConsensusError { + fn from(err: InvalidAssetLockProofValueError) -> Self { + Self::StateError(StateError::InvalidAssetLockProofValueError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/identity/mod.rs b/packages/rs-dpp/src/errors/consensus/state/identity/mod.rs index 01ec04bb52a..0aaa101f6c8 100644 --- a/packages/rs-dpp/src/errors/consensus/state/identity/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/state/identity/mod.rs @@ -9,6 +9,7 @@ pub mod identity_public_key_already_exists_for_unique_contract_bounds_error; pub mod identity_public_key_disabled_at_window_violation_error; pub mod identity_public_key_is_disabled_error; pub mod identity_public_key_is_read_only_error; +pub mod invalid_asset_lock_proof_value; pub mod invalid_identity_public_key_id_error; pub mod invalid_identity_revision_error; pub mod max_identity_public_key_limit_reached_error; diff --git a/packages/rs-dpp/src/errors/consensus/state/state_error.rs b/packages/rs-dpp/src/errors/consensus/state/state_error.rs index 4098b85042d..d79f5656e89 100644 --- a/packages/rs-dpp/src/errors/consensus/state/state_error.rs +++ b/packages/rs-dpp/src/errors/consensus/state/state_error.rs @@ -29,6 +29,7 @@ use crate::consensus::state::identity::{ }; use crate::consensus::ConsensusError; use crate::consensus::state::identity::identity_public_key_already_exists_for_unique_contract_bounds_error::IdentityPublicKeyAlreadyExistsForUniqueContractBoundsError; +use crate::consensus::state::identity::invalid_asset_lock_proof_value::InvalidAssetLockProofValueError; use super::document::document_timestamps_are_equal_error::DocumentTimestampsAreEqualError; @@ -85,6 +86,9 @@ pub enum StateError { #[error(transparent)] IdentityPublicKeyIsReadOnlyError(IdentityPublicKeyIsReadOnlyError), + #[error(transparent)] + InvalidAssetLockProofValueError(InvalidAssetLockProofValueError), + #[error(transparent)] MissingIdentityPublicKeyIdsError(MissingIdentityPublicKeyIdsError), diff --git a/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_create_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_create_transition/mod.rs index 91aab6d4997..15cd5a6ef49 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_create_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_create_transition/mod.rs @@ -68,6 +68,23 @@ impl IdentityCreateTransition { }), } } + + pub fn get_minimal_asset_lock_value( + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .state_transitions + .identities + .asset_locks + .minimal_asset_lock_value + { + 0 => Ok(MinimalAssetLockValue::V0 as u64), + v => Err(ProtocolError::UnknownVersionError(format!( + "Unknown IdentityCreateTransition version for minimal_asset_lock_value {v}" + ))), + } + } } impl StateTransitionFieldTypes for IdentityCreateTransition { @@ -83,3 +100,8 @@ impl StateTransitionFieldTypes for IdentityCreateTransition { vec![] } } + +#[repr(u64)] +pub enum MinimalAssetLockValue { + V0 = 120000, +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_topup_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_topup_transition/mod.rs index 21f72678d8a..fb009a867e2 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_topup_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/identity/identity_topup_transition/mod.rs @@ -67,6 +67,23 @@ impl IdentityTopUpTransition { }), } } + + pub fn get_minimal_asset_lock_value( + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .state_transitions + .identities + .asset_locks + .minimal_asset_lock_value + { + 0 => Ok(MinimalAssetLockValue::V0 as u64), + v => Err(ProtocolError::UnknownVersionError(format!( + "Unknown IdentityTopUpTransition version for minimal_asset_lock_value {v}" + ))), + } + } } impl StateTransitionFieldTypes for IdentityTopUpTransition { @@ -82,3 +99,8 @@ impl StateTransitionFieldTypes for IdentityTopUpTransition { vec![] } } + +#[repr(u64)] +enum MinimalAssetLockValue { + V0 = 1000, +} diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/common/asset_lock/transaction/fetch_asset_lock_transaction_output_sync/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/common/asset_lock/transaction/fetch_asset_lock_transaction_output_sync/v0/mod.rs index d4e70a394e8..2e3e1de1f19 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/common/asset_lock/transaction/fetch_asset_lock_transaction_output_sync/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/common/asset_lock/transaction/fetch_asset_lock_transaction_output_sync/v0/mod.rs @@ -114,7 +114,6 @@ pub fn fetch_asset_lock_transaction_output_sync_v0( // We are dealing with old Rust edition so we can't use optional remove if payload.credit_outputs.get(output_index as usize).is_some() { let output = payload.credit_outputs.remove(output_index as usize); - return Ok(ValidationResult::new_with_data(output)); } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create/state/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create/state/v0/mod.rs index ef090ac682b..04e89cb3675 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create/state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create/state/v0/mod.rs @@ -3,6 +3,7 @@ use crate::platform_types::platform::PlatformRef; use crate::rpc::core::CoreRPCLike; use dpp::consensus::signature::{BasicECDSAError, SignatureError}; +use dpp::consensus::state::identity::invalid_asset_lock_proof_value::InvalidAssetLockProofValueError; use dpp::consensus::state::identity::IdentityAlreadyExistsError; use dpp::dashcore::signer; use dpp::dashcore::signer::double_sha; @@ -12,6 +13,7 @@ use dpp::identity::state_transition::AssetLockProved; use dpp::prelude::ConsensusValidationResult; use dpp::serialization::Signable; use dpp::state_transition::identity_create_transition::accessors::IdentityCreateTransitionAccessorsV0; +use dpp::state_transition::identity_create_transition::methods::IdentityCreateTransitionMethodsV0; use dpp::state_transition::identity_create_transition::IdentityCreateTransition; use dpp::state_transition::{StateTransition, StateTransitionLike}; @@ -125,6 +127,12 @@ impl IdentityCreateStateTransitionStateValidationV0 for IdentityCreateTransition } let tx_out = tx_out_validation.into_data()?; + let min_value = IdentityCreateTransition::get_minimal_asset_lock_value(platform_version)?; + if tx_out.value < min_value { + return Ok(ConsensusValidationResult::new_with_error( + InvalidAssetLockProofValueError::new(tx_out.value, min_value).into(), + )); + } // Verify one time signature diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up/state/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up/state/v0/mod.rs index 66ece6b365d..a78c0724656 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up/state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up/state/v0/mod.rs @@ -3,6 +3,7 @@ use crate::platform_types::platform::PlatformRef; use crate::rpc::core::CoreRPCLike; use dpp::consensus::signature::{BasicECDSAError, SignatureError}; +use dpp::consensus::state::identity::invalid_asset_lock_proof_value::InvalidAssetLockProofValueError; use dpp::dashcore::signer; use dpp::dashcore::signer::double_sha; use dpp::identity::state_transition::AssetLockProved; @@ -10,6 +11,7 @@ use dpp::identity::KeyType; use dpp::prelude::ConsensusValidationResult; use dpp::serialization::Signable; +use dpp::state_transition::identity_topup_transition::methods::IdentityTopUpTransitionMethodsV0; use dpp::state_transition::identity_topup_transition::IdentityTopUpTransition; use dpp::state_transition::{StateTransition, StateTransitionLike}; @@ -88,6 +90,12 @@ impl IdentityTopUpStateTransitionStateValidationV0 for IdentityTopUpTransition { } let tx_out = tx_out_validation.into_data()?; + let min_value = IdentityTopUpTransition::get_minimal_asset_lock_value(platform_version)?; + if tx_out.value < min_value { + return Ok(ConsensusValidationResult::new_with_error( + InvalidAssetLockProofValueError::new(tx_out.value, min_value).into(), + )); + } // Verify one time signature diff --git a/packages/rs-platform-version/src/version/dpp_versions.rs b/packages/rs-platform-version/src/version/dpp_versions.rs index 6c271f17f2e..dd3999be5c2 100644 --- a/packages/rs-platform-version/src/version/dpp_versions.rs +++ b/packages/rs-platform-version/src/version/dpp_versions.rs @@ -29,6 +29,7 @@ pub struct IdentityTransitionVersions { pub struct IdentityTransitionAssetLockVersions { pub validate_asset_lock_transaction_structure: FeatureVersion, pub validate_instant_asset_lock_proof_structure: FeatureVersion, + pub minimal_asset_lock_value: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/mocks/v2_test.rs b/packages/rs-platform-version/src/version/mocks/v2_test.rs index a1a8d821439..3d9e442214f 100644 --- a/packages/rs-platform-version/src/version/mocks/v2_test.rs +++ b/packages/rs-platform-version/src/version/mocks/v2_test.rs @@ -827,6 +827,7 @@ pub(crate) const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { asset_locks: IdentityTransitionAssetLockVersions { validate_asset_lock_transaction_structure: 0, validate_instant_asset_lock_proof_structure: 0, + minimal_asset_lock_value: 0, }, }, }, diff --git a/packages/rs-platform-version/src/version/mocks/v3_test.rs b/packages/rs-platform-version/src/version/mocks/v3_test.rs index 4a87ecde428..da4bde11542 100644 --- a/packages/rs-platform-version/src/version/mocks/v3_test.rs +++ b/packages/rs-platform-version/src/version/mocks/v3_test.rs @@ -827,6 +827,7 @@ pub(crate) const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion { asset_locks: IdentityTransitionAssetLockVersions { validate_asset_lock_transaction_structure: 0, validate_instant_asset_lock_proof_structure: 0, + minimal_asset_lock_value: 0, }, }, }, diff --git a/packages/rs-platform-version/src/version/v1.rs b/packages/rs-platform-version/src/version/v1.rs index cc0fc782c6a..8ffb8fddb1e 100644 --- a/packages/rs-platform-version/src/version/v1.rs +++ b/packages/rs-platform-version/src/version/v1.rs @@ -824,6 +824,7 @@ pub(super) const PLATFORM_V1: PlatformVersion = PlatformVersion { asset_locks: IdentityTransitionAssetLockVersions { validate_asset_lock_transaction_structure: 0, validate_instant_asset_lock_proof_structure: 0, + minimal_asset_lock_value: 0, }, }, }, diff --git a/packages/wasm-dpp/src/errors/consensus/basic/identity/invalid_asset_lock_proof_value_error.rs b/packages/wasm-dpp/src/errors/consensus/basic/identity/invalid_asset_lock_proof_value_error.rs new file mode 100644 index 00000000000..18fbfb14368 --- /dev/null +++ b/packages/wasm-dpp/src/errors/consensus/basic/identity/invalid_asset_lock_proof_value_error.rs @@ -0,0 +1,39 @@ +use dpp::consensus::codes::ErrorWithCode; +use dpp::consensus::ConsensusError; + +use dpp::consensus::state::identity::invalid_asset_lock_proof_value::InvalidAssetLockProofValueError; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_name=InvalidAssetLockProofValueError)] +pub struct InvalidAssetLockProofValueErrorWasm { + inner: InvalidAssetLockProofValueError, +} + +impl From<&InvalidAssetLockProofValueError> for InvalidAssetLockProofValueErrorWasm { + fn from(e: &InvalidAssetLockProofValueError) -> Self { + Self { inner: e.clone() } + } +} + +#[wasm_bindgen(js_class=InvalidAssetLockProofValueError)] +impl InvalidAssetLockProofValueErrorWasm { + #[wasm_bindgen(js_name=getValue)] + pub fn value(&self) -> u64 { + self.inner.value() + } + + #[wasm_bindgen(js_name=getMinValue)] + pub fn min_value(&self) -> u64 { + self.inner.min_value() + } + + #[wasm_bindgen(js_name=getCode)] + pub fn get_code(&self) -> u32 { + ConsensusError::from(self.inner.clone()).code() + } + + #[wasm_bindgen(getter)] + pub fn message(&self) -> String { + self.inner.to_string() + } +} diff --git a/packages/wasm-dpp/src/errors/consensus/basic/identity/mod.rs b/packages/wasm-dpp/src/errors/consensus/basic/identity/mod.rs index 87dfbdc1ba6..dc1a57edc2a 100644 --- a/packages/wasm-dpp/src/errors/consensus/basic/identity/mod.rs +++ b/packages/wasm-dpp/src/errors/consensus/basic/identity/mod.rs @@ -8,6 +8,7 @@ mod identity_credit_transfer_to_self_error; mod identity_insufficient_balance_error; mod invalid_asset_lock_proof_core_chain_height_error; mod invalid_asset_lock_proof_transaction_height_error; +mod invalid_asset_lock_proof_value_error; mod invalid_asset_lock_transaction_output_return_size_error; mod invalid_identity_asset_lock_proof_chain_lock_validation_error; mod invalid_identity_asset_lock_transaction_error; @@ -34,6 +35,7 @@ pub use identity_credit_transfer_to_self_error::*; pub use identity_insufficient_balance_error::*; pub use invalid_asset_lock_proof_core_chain_height_error::*; pub use invalid_asset_lock_proof_transaction_height_error::*; +pub use invalid_asset_lock_proof_value_error::*; pub use invalid_asset_lock_transaction_output_return_size_error::*; pub use invalid_identity_asset_lock_proof_chain_lock_validation_error::*; pub use invalid_identity_asset_lock_transaction_error::*; diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index 2e88186f207..b2e514a2947 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -12,7 +12,7 @@ use crate::errors::consensus::basic::identity::{ IdentityAssetLockTransactionOutPointAlreadyExistsErrorWasm, IdentityAssetLockTransactionOutputNotFoundErrorWasm, IdentityCreditTransferToSelfErrorWasm, IdentityInsufficientBalanceErrorWasm, InvalidAssetLockProofCoreChainHeightErrorWasm, - InvalidAssetLockProofTransactionHeightErrorWasm, + InvalidAssetLockProofTransactionHeightErrorWasm, InvalidAssetLockProofValueErrorWasm, InvalidAssetLockTransactionOutputReturnSizeErrorWasm, InvalidIdentityAssetLockProofChainLockValidationErrorWasm, InvalidIdentityAssetLockTransactionErrorWasm, @@ -215,6 +215,9 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { StateError::DataContractConfigUpdateError(e) => { DataContractConfigUpdateErrorWasm::from(e).into() } + StateError::InvalidAssetLockProofValueError(e) => { + InvalidAssetLockProofValueErrorWasm::from(e).into() + } // TODO(versioning): restore _ => todo!(), }