diff --git a/packages/dapi/test/unit/externalApis/drive/fetchProofForStateTransitionFactory.spec.js b/packages/dapi/test/unit/externalApis/drive/fetchProofForStateTransitionFactory.spec.js index eae636a6bc3..442c41e3f86 100644 --- a/packages/dapi/test/unit/externalApis/drive/fetchProofForStateTransitionFactory.spec.js +++ b/packages/dapi/test/unit/externalApis/drive/fetchProofForStateTransitionFactory.spec.js @@ -109,7 +109,7 @@ describe('fetchProofForStateTransition', () => { const contractId = documents[0].getDataContractId(); const transition = dpp.document.createStateTransition({ - create: documents, + create: documents.map((d) => ({ document: d })), }, { [identityId.toString()]: { [contractId.toString()]: '1', diff --git a/packages/js-dash-sdk/docs/platform/documents/broadcast.md b/packages/js-dash-sdk/docs/platform/documents/broadcast.md index 67e59a2d991..8ee2238d0ed 100644 --- a/packages/js-dash-sdk/docs/platform/documents/broadcast.md +++ b/packages/js-dash-sdk/docs/platform/documents/broadcast.md @@ -3,13 +3,17 @@ Parameters: -| parameters | type | required | Description | -|----------------------------|------------|----------|------------------------------------------------------------------------------| -| **documents** | Object | yes | | -| **documents.create** | ExtendedDocument[] | no | array of valid [created document](../documents/create.md) to create | -| **documents.replace** | ExtendedDocument[] | no | array of valid [created document](../documents/create.md) to replace | -| **documents.delete** | ExtendedDocument[] | no | array of valid [created document](../documents/create.md) to delete | -| **identity** | Identity | yes | A valid [registered identity](../identities/register.md) | +| parameters | type | required | Description | +|---------------------------|--------------------|----------|----------------------------------------------------------------------------------------| +| **documents** | Object | yes | | +| **documents.create** | ExtendedDocument[] | no | array of valid [created document](../documents/create.md) to create | +| **documents.replace** | ExtendedDocument[] | no | array of valid [created document](../documents/create.md) to replace | +| **documents.delete** | ExtendedDocument[] | no | array of valid [created document](../documents/create.md) to delete | +| **documents.transfer** | ExtendedDocument[] | no | array of valid [created document](../documents/create.md) to transfer | +| **documents.updatePrice** | ExtendedDocument[] | no | array of valid [created document](../documents/create.md) to set price | +| **documents.purchase** | ExtendedDocument[] | no | array of valid [created document](../documents/create.md) to purchase | +| **identity** | Identity | yes | A valid [registered identity](../identities/register.md) | +| **options** | DocumentTransitionParams | no | An object with two optional fields `price` and `receiver` that is used for NFT actions | **Example**: diff --git a/packages/js-dash-sdk/docs/platform/documents/purchase.md b/packages/js-dash-sdk/docs/platform/documents/purchase.md new file mode 100644 index 00000000000..96269296f0b --- /dev/null +++ b/packages/js-dash-sdk/docs/platform/documents/purchase.md @@ -0,0 +1,33 @@ +**Usage**: `client.platform.documents.broadcast(documents, identity, options)` +**Description**: This method will broadcast a purchase state transition that buys the given document from other Identity. + +Parameters: + +| parameters | type | required | Description | +|------------------------|---------|------------------ |-------------------------------------------------------------------| +| **documents.purchase** | ExtendedDocument[] | no | array of valid [created document](../documents/create.md) to buy | +| **identity** | Identity | yes | A valid [registered identity](../identities/register.md) | +| **options** | DocumentTransitionParams | no | An object with field `price` (BigInt) and `receiver` (Identifier) | + +**Example**: +```js +const identityId = '';// Your identity identifier +const receiverId = ''; // Receiver identity identifier +const documentId = '' // Your document id +const price = BigInt(1000000) + +const identity = await client.platform.identities.get(identityId); +const receiverIdentity = await client.platform.identities.get(receiverId); + +const identity = await client.platform.identities.get(identityId); + +const [document] = await dash.platform.documents.get( + 'helloWorldContract.note', + { where: [['$id', '==', documentId]] }, +); + +await dash.platform.documents.broadcast({ purchase: [document], }, identity, { price, receiver: receiverIdentity.getId() }); +``` +**Note**: This method will change the ownership of the document to your identity, and seller identity will be credited with the amount specified in the updatePrice deducted from your balance. + +Returns: DocumentsBatchTransition diff --git a/packages/js-dash-sdk/docs/platform/documents/transfer.md b/packages/js-dash-sdk/docs/platform/documents/transfer.md new file mode 100644 index 00000000000..9200618a547 --- /dev/null +++ b/packages/js-dash-sdk/docs/platform/documents/transfer.md @@ -0,0 +1,31 @@ +**Usage**: `client.platform.documents.broadcast(documents, identity, options)` +**Description**: This method will broadcast a document transfer + +Parameters: + +| parameters | type | required | Description | +|-------------------|---------|------------------ |-----------------------------------------------------------------------| +| **documents.transfer** | ExtendedDocument[] | no | array of valid [created document](../documents/create.md) to transfer | +| **identity** | Identity | yes | A valid [registered identity](../identities/register.md) | +| **options** | DocumentTransitionParams | no | An object with `receiver` field | + +**Example**: +```js +const identityId = '';// Your identity identifier +const receiverId = ''; // Receiver identity identifier +const documentId = '' // Your document id + +const identity = await client.platform.identities.get(identityId); +const receiverIdentity = await client.platform.identities.get(receiverId); + +const [document] = await dash.platform.documents.get( + 'helloWorldContract.note', + { where: [['$id', '==', documentId]] }, +); + +await dash.platform.documents.broadcast({ transfer: [document], }, identity, { receiver: receiverIdentity.getId() }); +``` + +**Note**: Transfer transition changes the ownership of the given document to the receiver identity + +Returns: DocumentsBatchTransition diff --git a/packages/js-dash-sdk/docs/platform/documents/updatePrice.md b/packages/js-dash-sdk/docs/platform/documents/updatePrice.md new file mode 100644 index 00000000000..d1c4c284b10 --- /dev/null +++ b/packages/js-dash-sdk/docs/platform/documents/updatePrice.md @@ -0,0 +1,29 @@ +**Usage**: `client.platform.documents.broadcast(documents, identity, options)` +**Description**: This method will broadcast an update price state transition that sets a price for the given document. + +Parameters: + +| parameters | type | required | Description | +|---------------------------|---------|------------------ |---------------------------------------------------------------------------| +| **documents.updatePrice** | ExtendedDocument[] | no | array of valid [created document](../documents/create.md) to update price | +| **identity** | Identity | yes | A valid [registered identity](../identities/register.md) | +| **options** | DocumentTransitionParams | no | An object with field `price` (BigInt) | + +**Example**: +```js +const identityId = '';// Your identity identifier +const documentId = '' // Your document id +const price = BigInt(1000000) + +const identity = await client.platform.identities.get(identityId); + +const [document] = await dash.platform.documents.get( + 'helloWorldContract.note', + { where: [['$id', '==', documentId]] }, +); + +await dash.platform.documents.broadcast({ updatePrice: [document], }, identity, { price }); +``` +**Note**: This method sets the same price on all documents in the batch (only one is possible right now) + +Returns: DocumentsBatchTransition diff --git a/packages/js-dash-sdk/src/SDK/Client/Client.spec.ts b/packages/js-dash-sdk/src/SDK/Client/Client.spec.ts index bc6fa96bd42..25765401c72 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Client.spec.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Client.spec.ts @@ -313,7 +313,7 @@ describe('Dash - Client', function suite() { let error; try { await client.platform.documents.broadcast({ - create: documentsFixture, + create: documentsFixture.map((d) => ({ document: d, params: null })), }, identityFixture); } catch (e) { error = e; @@ -332,7 +332,7 @@ describe('Dash - Client', function suite() { dapiClientMock.platform.waitForStateTransitionResult.resolves(proofResponse); await client.platform.documents.broadcast({ - create: documentsFixture, + create: documentsFixture.map((d) => ({ document: d, params: null })), }, identityFixture); const serializedSt = dapiClientMock.platform.broadcastStateTransition.getCall(0).args[0]; diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/documents/broadcast.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/documents/broadcast.ts index 4a6a7a06026..3b899de321b 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/documents/broadcast.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/documents/broadcast.ts @@ -1,24 +1,39 @@ -import { ExtendedDocument } from '@dashevo/wasm-dpp'; +import { ExtendedDocument, Identifier } from '@dashevo/wasm-dpp'; import { Platform } from '../../Platform'; import broadcastStateTransition from '../../broadcastStateTransition'; import { signStateTransition } from '../../signStateTransition'; +interface DocumentTransitionParams { + receiver?: Identifier; + price?: bigint; +} + +interface DocumentSubmittable { + document: ExtendedDocument; + params?: DocumentTransitionParams; +} + /** * Broadcast document onto the platform * - * @param {Platform} this - bound instance class * @param {Object} documents - * @param {ExtendedDocument[]} [documents.create] - * @param {ExtendedDocument[]} [documents.replace] - * @param {ExtendedDocument[]} [documents.delete] - * @param identity - identity + * @param {DocumentSubmittable[]} [documents.create] + * @param {DocumentSubmittable[]} [documents.replace] + * @param {DocumentSubmittable[]} [documents.delete] + * @param {DocumentSubmittable[]} [documents.transfer] + * @param {DocumentSubmittable[]} [documents.updatePrice] + * @param {DocumentSubmittable[]} [documents.purchase] + * @param {Identity} identity */ export default async function broadcast( this: Platform, documents: { - create?: ExtendedDocument[], - replace?: ExtendedDocument[], - delete?: ExtendedDocument[] + create?: DocumentSubmittable[], + replace?: DocumentSubmittable[], + delete?: DocumentSubmittable[], + transfer?: DocumentSubmittable[], + updatePrice?: DocumentSubmittable[], + purchase?: DocumentSubmittable[], }, identity: any, ): Promise { @@ -26,6 +41,9 @@ export default async function broadcast( create: documents.create?.length || 0, replace: documents.replace?.length || 0, delete: documents.delete?.length || 0, + transfer: documents.transfer?.length || 0, + updatePrice: documents.updatePrice?.length || 0, + purchase: documents.purchase?.length || 0, }); await this.initialize(); @@ -36,20 +54,47 @@ export default async function broadcast( ...(documents.create || []), ...(documents.replace || []), ...(documents.delete || []), - ][0]?.getDataContractId(); + ...(documents.transfer || []), + ...(documents.updatePrice || []), + ...(documents.purchase || []), + ][0]?.document.getDataContractId(); if (!dataContractId) { throw new Error('Data contract ID is not found'); } + if (documents.transfer?.length && documents.transfer + .some(({ params }) => !params?.receiver)) { + throw new Error('Receiver Identity is not found for Transfer transition'); + } + + if (documents.updatePrice?.length && documents.updatePrice + .some(({ params }) => !params?.price)) { + throw new Error('Price must be provided for UpdatePrice operation'); + } + + if (documents.purchase?.length) { + if (documents.purchase + .some(({ params }) => !params?.price || !params?.receiver)) { + throw new Error('Receiver and Price must be provided for Purchase operation'); + } else { + documents.purchase.forEach(({ document, params }) => document.setOwnerId(params!.receiver)); + } + } + const identityContractNonce = await this.nonceManager .bumpIdentityContractNonce(identityId, dataContractId); - const documentsBatchTransition = dpp.document.createStateTransition(documents, { + const identityNonceObj = { [identityId.toString()]: { [dataContractId.toString()]: identityContractNonce.toString(), }, - }); + }; + + const documentsBatchTransition = dpp.document.createStateTransition( + documents, + identityNonceObj, + ); this.logger.silly('[Document#broadcast] Created documents batch transition'); @@ -61,7 +106,7 @@ export default async function broadcast( // Acknowledge documents identifiers to handle retry attempts to mitigate // state transition propagation lag if (documents.create) { - documents.create.forEach((document) => { + documents.create.forEach(({ document }) => { const documentLocator = `${document.getDataContractId().toString()}/${document.getType()}`; this.fetcher.acknowledgeKey(documentLocator); }); @@ -69,7 +114,7 @@ export default async function broadcast( // Forget documents identifiers to not retry on them anymore if (documents.delete) { - documents.delete.forEach((document) => { + documents.delete.forEach(({ document }) => { const documentLocator = `${document.getDataContractId().toString()}/${document.getType()}`; this.fetcher.forgetKey(documentLocator); }); @@ -79,6 +124,7 @@ export default async function broadcast( create: documents.create?.length || 0, replace: documents.replace?.length || 0, delete: documents.delete?.length || 0, + transfer: documents.transfer?.length || 0, }); return documentsBatchTransition; diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/register.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/register.ts index 07f6576e782..6bb0cc964a3 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/register.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/names/register.ts @@ -74,7 +74,7 @@ export async function register( await this.documents.broadcast( { - create: [preorderDocument], + create: [{ document: preorderDocument }], }, identity, ); @@ -99,7 +99,7 @@ export async function register( // 4. Create and send domain state transition await this.documents.broadcast( { - create: [domainDocument], + create: [{ document: domainDocument }], }, identity, ); diff --git a/packages/platform-test-suite/test/e2e/contacts.spec.js b/packages/platform-test-suite/test/e2e/contacts.spec.js index fe8eaf2fced..14be962f707 100644 --- a/packages/platform-test-suite/test/e2e/contacts.spec.js +++ b/packages/platform-test-suite/test/e2e/contacts.spec.js @@ -145,7 +145,7 @@ describe('e2e', () => { }); await bobClient.platform.documents.broadcast({ - create: [profile], + create: [{ document: profile }], }, bobIdentity); // Additional wait time to mitigate testnet latency @@ -244,7 +244,7 @@ describe('e2e', () => { }); await aliceClient.platform.documents.broadcast({ - create: [aliceProfile], + create: [{ document: aliceProfile }], }, aliceIdentity); // Additional wait time to mitigate testnet latency @@ -265,7 +265,7 @@ describe('e2e', () => { // 2. Broadcast change await aliceClient.platform.documents.broadcast({ - replace: [aliceProfile], + replace: [{ document: aliceProfile }], }, aliceIdentity); // Additional wait time to mitigate testnet latency @@ -299,7 +299,7 @@ describe('e2e', () => { }); await bobClient.platform.documents.broadcast({ - create: [bobContactRequest], + create: [{ document: bobContactRequest }], }, bobIdentity); // Additional wait time to mitigate testnet latency @@ -324,7 +324,7 @@ describe('e2e', () => { }); await aliceClient.platform.documents.broadcast({ - create: [aliceContactAcceptance], + create: [{ document: aliceContactAcceptance }], }, aliceIdentity); // Additional wait time to mitigate testnet latency @@ -344,7 +344,7 @@ describe('e2e', () => { it('should be able to remove contact approval', async () => { // 1. Broadcast document deletion await aliceClient.platform.documents.broadcast({ - delete: [aliceContactAcceptance], + delete: [{ document: aliceContactAcceptance }], }, aliceIdentity); // Additional wait time to mitigate testnet latency diff --git a/packages/platform-test-suite/test/e2e/dpns.spec.js b/packages/platform-test-suite/test/e2e/dpns.spec.js index 786f583f873..12107e24885 100644 --- a/packages/platform-test-suite/test/e2e/dpns.spec.js +++ b/packages/platform-test-suite/test/e2e/dpns.spec.js @@ -260,7 +260,7 @@ describe('DPNS', () => { try { await client.platform.documents.broadcast({ - delete: [registeredDomain], + delete: [{ document: registeredDomain }], }, identity); } catch (e) { broadcastError = e; diff --git a/packages/platform-test-suite/test/e2e/withdrawals.spec.js b/packages/platform-test-suite/test/e2e/withdrawals.spec.js index b7afc0fb2c7..a19968f158e 100644 --- a/packages/platform-test-suite/test/e2e/withdrawals.spec.js +++ b/packages/platform-test-suite/test/e2e/withdrawals.spec.js @@ -114,7 +114,7 @@ describe('Withdrawals', function withdrawalsTest() { // Should allow deleting of the withdrawal document await client.platform.documents.broadcast({ - delete: [withdrawalDocument], + delete: [{ document: withdrawalDocument }], }, identity); }); @@ -216,7 +216,7 @@ describe('Withdrawals', function withdrawalsTest() { try { await client.platform.documents.broadcast({ - create: [withdrawal], + create: [{ document: withdrawal }], }, identity); expect.fail('should throw broadcast error'); @@ -260,7 +260,7 @@ describe('Withdrawals', function withdrawalsTest() { try { await client.platform.documents.broadcast({ - delete: [withdrawalDocument], + delete: [{ document: withdrawalDocument }], }, identity); expect.fail('should throw broadcast error'); @@ -283,7 +283,7 @@ describe('Withdrawals', function withdrawalsTest() { try { await client.platform.documents.broadcast({ - replace: [withdrawalDocument], + replace: [{ document: withdrawalDocument }], }, identity); expect.fail('should throw broadcast error'); diff --git a/packages/platform-test-suite/test/functional/platform/DataContract.spec.js b/packages/platform-test-suite/test/functional/platform/DataContract.spec.js index 28fe50b14ec..3d29c5add32 100644 --- a/packages/platform-test-suite/test/functional/platform/DataContract.spec.js +++ b/packages/platform-test-suite/test/functional/platform/DataContract.spec.js @@ -206,7 +206,7 @@ describe('Platform', () => { ); await client.platform.documents.broadcast({ - create: [document], + create: [{ document }], }, identity); // Additional wait time to mitigate testnet latency diff --git a/packages/platform-test-suite/test/functional/platform/Document.spec.js b/packages/platform-test-suite/test/functional/platform/Document.spec.js index 85f9abf963a..5ddb3ce5618 100644 --- a/packages/platform-test-suite/test/functional/platform/Document.spec.js +++ b/packages/platform-test-suite/test/functional/platform/Document.spec.js @@ -101,7 +101,7 @@ describe('Platform', () => { try { await client.platform.documents.broadcast({ - create: [newDocument], + create: [{ document: newDocument }], }, identity); } catch (e) { broadcastError = e; @@ -134,7 +134,7 @@ describe('Platform', () => { try { await client.platform.documents.broadcast({ - create: [document], + create: [{ document }], }, unknownIdentity); } catch (e) { broadcastError = e; @@ -165,7 +165,7 @@ describe('Platform', () => { ); await client.platform.documents.broadcast({ - create: [firstDocument], + create: [{ document: firstDocument }], }, identity); // Additional wait time to mitigate testnet latency @@ -184,7 +184,7 @@ describe('Platform', () => { try { await client.platform.documents.broadcast({ - create: [secondDocument], + create: [{ document: secondDocument }], }, identity); } catch (e) { broadcastError = e; @@ -213,7 +213,7 @@ describe('Platform', () => { ); await client.platform.documents.broadcast({ - create: [document], + create: [{ document }], }, identity); // Additional wait time to mitigate testnet latency @@ -262,7 +262,7 @@ describe('Platform', () => { storedDocument.set('firstName', 'updatedName'); await client.platform.documents.broadcast({ - replace: [storedDocument], + replace: [{ document: storedDocument }], }, identity); // Additional wait time to mitigate testnet latency @@ -292,7 +292,7 @@ describe('Platform', () => { storedDocument.set('firstName', 'updatedName'); const documentsBatchTransition = await client.platform.documents.broadcast({ - replace: [storedDocument], + replace: [{ document: storedDocument }], }, identity); // Additional wait time to mitigate testnet latency @@ -368,7 +368,7 @@ describe('Platform', () => { it('should be able to delete a document', async () => { await client.platform.documents.broadcast({ - delete: [document], + delete: [{ document }], }, identity); await waitForSTPropagated(); 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 8f5e8994c40..fb2a76b8f90 100644 --- a/packages/platform-test-suite/test/functional/platform/Identity.spec.js +++ b/packages/platform-test-suite/test/functional/platform/Identity.spec.js @@ -358,7 +358,7 @@ describe('Platform', () => { try { await client.platform.documents.broadcast({ - create: [document], + create: [{ document }], }, lowBalanceIdentity); } catch (e) { broadcastError = e; @@ -445,7 +445,7 @@ describe('Platform', () => { ); await client.platform.documents.broadcast({ - create: [document], + create: [{ document }], }, identity); // Additional wait time to mitigate testnet latency diff --git a/packages/platform-test-suite/test/functional/platform/featureFlags.spec.js b/packages/platform-test-suite/test/functional/platform/featureFlags.spec.js index 2325a8ac7b1..b024c93bad5 100644 --- a/packages/platform-test-suite/test/functional/platform/featureFlags.spec.js +++ b/packages/platform-test-suite/test/functional/platform/featureFlags.spec.js @@ -103,12 +103,12 @@ describe.skip('Platform', () => { ); await ownerClient.platform.documents.broadcast({ - create: [documentUpdate], + create: [{ document: documentUpdate }], }, identity); // forcing creation of additional block (enableAtHeight + 1) await ownerClient.platform.documents.broadcast({ - create: [documentRevert], + create: [{ document: documentRevert }], }, identity); // forcing creation of additional block (enableAtHeight + 2) diff --git a/packages/rs-dpp/src/document/document_factory/mod.rs b/packages/rs-dpp/src/document/document_factory/mod.rs index 158daf9488f..abc1bb6945b 100644 --- a/packages/rs-dpp/src/document/document_factory/mod.rs +++ b/packages/rs-dpp/src/document/document_factory/mod.rs @@ -9,9 +9,9 @@ use derive_more::From; use platform_value::{Bytes32, Identifier, Value}; use crate::data_contract::document_type::DocumentTypeRef; -use crate::document::Document; #[cfg(feature = "extended-document")] use crate::document::ExtendedDocument; +use crate::document::{Document, DocumentTransitionParams}; #[cfg(feature = "state-transitions")] use crate::state_transition::batch_transition::{ batched_transition::document_transition_action_type::DocumentTransitionActionType, @@ -117,7 +117,12 @@ impl DocumentFactory { documents_iter: impl IntoIterator< Item = ( DocumentTransitionActionType, - Vec<(Document, DocumentTypeRef<'a>, Bytes32)>, + Vec<( + Document, + Option, + DocumentTypeRef<'a>, + Bytes32, + )>, ), >, nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce diff --git a/packages/rs-dpp/src/document/document_factory/v0/mod.rs b/packages/rs-dpp/src/document/document_factory/v0/mod.rs index 5fa252dc973..45c2f377703 100644 --- a/packages/rs-dpp/src/document/document_factory/v0/mod.rs +++ b/packages/rs-dpp/src/document/document_factory/v0/mod.rs @@ -5,7 +5,9 @@ use crate::data_contract::document_type::DocumentTypeRef; use crate::data_contract::errors::DataContractError; use crate::data_contract::DataContract; use crate::document::errors::DocumentError; -use crate::document::{Document, DocumentV0Getters, DocumentV0Setters, INITIAL_REVISION}; +use crate::document::{ + Document, DocumentTransitionParams, DocumentV0Getters, DocumentV0Setters, INITIAL_REVISION, +}; use chrono::Utc; use std::collections::BTreeMap; @@ -20,20 +22,23 @@ use crate::document::document_methods::DocumentMethodsV0; #[cfg(feature = "extended-document")] use crate::document::{ extended_document::v0::ExtendedDocumentV0, - ExtendedDocument, serialization_traits::DocumentPlatformConversionMethodsV0, + serialization_traits::DocumentPlatformConversionMethodsV0, ExtendedDocument, }; use crate::prelude::{BlockHeight, CoreBlockHeight, TimestampMillis}; #[cfg(feature = "state-transitions")] +use crate::state_transition::batch_transition::batched_transition::document_transition::DocumentTransition; +#[cfg(feature = "state-transitions")] use crate::state_transition::batch_transition::{ batched_transition::{ document_transition_action_type::DocumentTransitionActionType, DocumentCreateTransition, - DocumentDeleteTransition, DocumentReplaceTransition, + DocumentDeleteTransition, DocumentPurchaseTransition, DocumentReplaceTransition, }, BatchTransition, BatchTransitionV0, }; +use crate::state_transition::state_transitions::document::batch_transition::batched_transition::{ + DocumentTransferTransition, DocumentUpdatePriceTransition, +}; use itertools::Itertools; -#[cfg(feature = "state-transitions")] -use crate::state_transition::state_transitions::document::batch_transition::batched_transition::document_transition::DocumentTransition; const PROPERTY_FEATURE_VERSION: &str = "$version"; const PROPERTY_ENTROPY: &str = "$entropy"; @@ -208,7 +213,12 @@ impl DocumentFactoryV0 { documents_iter: impl IntoIterator< Item = ( DocumentTransitionActionType, - Vec<(Document, DocumentTypeRef<'a>, Bytes32)>, + Vec<( + Document, + Option, + DocumentTypeRef<'a>, + Bytes32, + )>, ), >, nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce @@ -216,25 +226,30 @@ impl DocumentFactoryV0 { let platform_version = PlatformVersion::get(self.protocol_version)?; let documents: Vec<( DocumentTransitionActionType, - Vec<(Document, DocumentTypeRef, Bytes32)>, + Vec<( + Document, + Option, + DocumentTypeRef, + Bytes32, + )>, )> = documents_iter.into_iter().collect(); let mut flattened_documents_iter = documents.iter().flat_map(|(_, v)| v).peekable(); - let Some((first_document, _, _)) = flattened_documents_iter.peek() else { + let Some((first_document, _, _, _)) = flattened_documents_iter.peek() else { return Err(DocumentError::NoDocumentsSuppliedError.into()); }; let owner_id = first_document.owner_id(); let is_the_same_owner = - flattened_documents_iter.all(|(document, _, _)| document.owner_id() == owner_id); + flattened_documents_iter.all(|(document, _, _, _)| document.owner_id() == owner_id); if !is_the_same_owner { return Err(DocumentError::MismatchOwnerIdsError { documents: documents .into_iter() .flat_map(|(_, v)| { v.into_iter() - .map(|(document, _, _)| document) + .map(|(document, _, _, _)| document) .collect::>() }) .collect(), @@ -245,13 +260,18 @@ impl DocumentFactoryV0 { let transitions: Vec<_> = documents .into_iter() .map(|(action, documents)| match action { - DocumentTransitionActionType::Create => { - Self::document_create_transitions(documents, nonce_counter, platform_version) - } + DocumentTransitionActionType::Create => Self::document_create_transitions( + documents + .into_iter() + .map(|(document, _, document_type, bytes)| (document, document_type, bytes)) + .collect(), + nonce_counter, + platform_version, + ), DocumentTransitionActionType::Delete => Self::document_delete_transitions( documents .into_iter() - .map(|(document, document_type, _)| (document, document_type)) + .map(|(document, _, document_type, _)| (document, document_type)) .collect(), nonce_counter, platform_version, @@ -259,14 +279,50 @@ impl DocumentFactoryV0 { DocumentTransitionActionType::Replace => Self::document_replace_transitions( documents .into_iter() - .map(|(document, document_type, _)| (document, document_type)) + .map(|(document, _, document_type, _)| (document, document_type)) + .collect(), + nonce_counter, + platform_version, + ), + DocumentTransitionActionType::Transfer => Self::document_transfer_transitions( + documents + .into_iter() + .map(|(document, document_params, document_type, _)| { + (document, document_params.unwrap(), document_type) + }) .collect(), nonce_counter, platform_version, ), - _ => Err(ProtocolError::InvalidStateTransitionType( - "action type not accounted for".to_string(), - )), + DocumentTransitionActionType::UpdatePrice => { + Self::document_update_price_transitions( + documents + .into_iter() + .map(|(document, document_params, document_type, _)| { + (document, document_params.unwrap(), document_type) + }) + .collect(), + nonce_counter, + platform_version, + ) + } + DocumentTransitionActionType::Purchase => Self::document_purchase_transitions( + documents + .into_iter() + .map(|(document, document_params, document_type, _)| { + (document, document_params.unwrap(), document_type) + }) + .collect(), + nonce_counter, + platform_version, + ), + _ => { + let action_type_name: &str = action.into(); + + Err(ProtocolError::InvalidStateTransitionType( + action_type_name.to_string(), + )) + } }) .collect::, ProtocolError>>()? .into_iter() @@ -550,6 +606,138 @@ impl DocumentFactoryV0 { .collect() } + #[cfg(feature = "state-transitions")] + fn document_transfer_transitions( + documents: Vec<(Document, DocumentTransitionParams, DocumentTypeRef)>, + nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce + platform_version: &PlatformVersion, + ) -> Result, ProtocolError> { + documents + .into_iter() + .map(|(mut document, document_params, document_type)| { + if !document_type.documents_transferable().is_transferable() { + return Err(DocumentError::TryingToTransferNonTransferableDocument { + document: Box::new(document), + } + .into()); + } + let Some(_document_revision) = document.revision() else { + return Err(DocumentError::RevisionAbsentError { + document: Box::new(document), + } + .into()); + }; + + document.increment_revision()?; + document.set_updated_at(Some(Utc::now().timestamp_millis() as TimestampMillis)); + + let nonce = nonce_counter + .entry((document.owner_id(), document_type.data_contract_id())) + .or_default(); + + let transition = DocumentTransferTransition::from_document( + document, + document_type, + *nonce, + document_params.receiver.unwrap(), + platform_version, + None, + None, + )?; + + *nonce += 1; + + Ok(transition.into()) + }) + .collect() + } + + #[cfg(feature = "state-transitions")] + fn document_update_price_transitions( + documents: Vec<(Document, DocumentTransitionParams, DocumentTypeRef)>, + nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce + platform_version: &PlatformVersion, + ) -> Result, ProtocolError> { + documents + .into_iter() + .map(|(mut document, document_params, document_type)| { + let Some(_document_revision) = document.revision() else { + return Err(DocumentError::RevisionAbsentError { + document: Box::new(document), + } + .into()); + }; + + let nonce = nonce_counter + .entry((document.owner_id(), document_type.data_contract_id())) + .or_default(); + + let now = Utc::now().timestamp_millis() as TimestampMillis; + + document.increment_revision()?; + document.set_updated_at(Some(now)); + document.set_transferred_at(Some(now)); + + let transition = DocumentUpdatePriceTransition::from_document( + document, + document_type, + document_params.price.unwrap(), + *nonce, + platform_version, + None, + None, + )?; + + *nonce += 1; + + Ok(transition.into()) + }) + .collect() + } + + #[cfg(feature = "state-transitions")] + fn document_purchase_transitions( + documents: Vec<(Document, DocumentTransitionParams, DocumentTypeRef)>, + nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce + platform_version: &PlatformVersion, + ) -> Result, ProtocolError> { + documents + .into_iter() + .map(|(mut document, document_params, document_type)| { + let Some(_document_revision) = document.revision() else { + return Err(DocumentError::RevisionAbsentError { + document: Box::new(document), + } + .into()); + }; + + let nonce = nonce_counter + .entry(( + document_params.receiver.unwrap(), + document_type.data_contract_id(), + )) + .or_default(); + + document.increment_revision()?; + document.set_updated_at(Some(Utc::now().timestamp_millis() as TimestampMillis)); + + let transition = DocumentPurchaseTransition::from_document( + document, + document_type, + document_params.price.unwrap(), + *nonce, + platform_version, + None, + None, + )?; + + *nonce += 1; + + Ok(transition.into()) + }) + .collect() + } + fn is_ownership_the_same<'a>(ids: impl IntoIterator) -> bool { ids.into_iter().all_equal() } diff --git a/packages/rs-dpp/src/document/errors.rs b/packages/rs-dpp/src/document/errors.rs index 9df0c53b500..df9374e6b14 100644 --- a/packages/rs-dpp/src/document/errors.rs +++ b/packages/rs-dpp/src/document/errors.rs @@ -47,6 +47,9 @@ pub enum DocumentError { #[error("Trying to delete indelible document")] TryingToDeleteIndelibleDocument { document: Box }, + #[error("Trying to transfer non-transferable document")] + TryingToTransferNonTransferableDocument { document: Box }, + #[error("Documents have mixed owner ids")] MismatchOwnerIdsError { documents: Vec }, diff --git a/packages/rs-dpp/src/document/mod.rs b/packages/rs-dpp/src/document/mod.rs index 29d2ec24a32..19212535280 100644 --- a/packages/rs-dpp/src/document/mod.rs +++ b/packages/rs-dpp/src/document/mod.rs @@ -45,6 +45,7 @@ use derive_more::From; #[cfg(feature = "document-serde-conversion")] use serde::{Deserialize, Serialize}; +use platform_value::Identifier; use std::fmt; use std::fmt::Formatter; @@ -59,6 +60,12 @@ pub enum Document { V0(DocumentV0), } +#[derive(Clone)] +pub struct DocumentTransitionParams { + pub receiver: Option, + pub price: Option, +} + impl fmt::Display for Document { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { diff --git a/packages/rs-dpp/src/state_transition/serialization.rs b/packages/rs-dpp/src/state_transition/serialization.rs index ff55d2653ab..164bd208d2e 100644 --- a/packages/rs-dpp/src/state_transition/serialization.rs +++ b/packages/rs-dpp/src/state_transition/serialization.rs @@ -331,6 +331,7 @@ mod tests { let data_contract = extended_document.data_contract(); ( document, + None, data_contract .document_type_for_name(extended_document.document_type_name()) .unwrap(), diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_transition_action_type.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_transition_action_type.rs index 2fadc908381..f2892d0a886 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_transition_action_type.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_transition_action_type.rs @@ -47,3 +47,19 @@ impl TryFrom<&str> for DocumentTransitionActionType { } } } + +impl From for &str { + fn from(value: DocumentTransitionActionType) -> Self { + match value { + DocumentTransitionActionType::Create => "Create", + DocumentTransitionActionType::Replace => "Replace", + DocumentTransitionActionType::Delete => "Delete", + DocumentTransitionActionType::Transfer => "Transfer", + DocumentTransitionActionType::UpdatePrice => "UpdatePrice", + DocumentTransitionActionType::Purchase => "Purchase", + DocumentTransitionActionType::IgnoreWhileBumpingRevision => { + "IgnoreWhileBumpingRevision" + } + } + } +} diff --git a/packages/rs-dpp/src/tests/fixtures/get_document_transitions_fixture.rs b/packages/rs-dpp/src/tests/fixtures/get_document_transitions_fixture.rs index d50516c4400..815359a872a 100644 --- a/packages/rs-dpp/src/tests/fixtures/get_document_transitions_fixture.rs +++ b/packages/rs-dpp/src/tests/fixtures/get_document_transitions_fixture.rs @@ -4,7 +4,7 @@ use platform_value::{Bytes32, Identifier}; use platform_version::version::PlatformVersion; use std::collections::BTreeMap; -use crate::document::Document; +use crate::document::{Document, DocumentTransitionParams}; use crate::state_transition::batch_transition::accessors::DocumentsBatchTransitionAccessorsV0; use crate::state_transition::batch_transition::batched_transition::BatchedTransition; use crate::state_transition::batch_transition::batched_transition::document_transition_action_type::DocumentTransitionActionType; @@ -12,7 +12,12 @@ pub fn get_batched_transitions_fixture<'a>( documents: impl IntoIterator< Item = ( DocumentTransitionActionType, - Vec<(Document, DocumentTypeRef<'a>, Bytes32)>, + Vec<( + Document, + Option, + DocumentTypeRef<'a>, + Bytes32, + )>, ), >, nonce_counter: &mut BTreeMap<(Identifier, Identifier), u64>, //IdentityID/ContractID -> nonce diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dashpay/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dashpay/v0/mod.rs index d3cdf285133..3c43a287de2 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dashpay/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dashpay/v0/mod.rs @@ -154,7 +154,12 @@ mod test { let document_transitions = get_batched_transitions_fixture( [( DocumentTransitionActionType::Create, - vec![(contact_request_document, document_type, Bytes32::default())], + vec![( + contact_request_document, + None, + document_type, + Bytes32::default(), + )], )], &mut nonce_counter, ); @@ -257,7 +262,12 @@ mod test { let document_transitions = get_batched_transitions_fixture( [( DocumentTransitionActionType::Create, - vec![(contact_request_document, document_type, Bytes32::default())], + vec![( + contact_request_document, + None, + document_type, + Bytes32::default(), + )], )], &mut nonce_counter, ); @@ -385,7 +395,12 @@ mod test { let document_transitions = get_batched_transitions_fixture( [( DocumentTransitionActionType::Create, - vec![(contact_request_document, document_type, Bytes32::default())], + vec![( + contact_request_document, + None, + document_type, + Bytes32::default(), + )], )], &mut nonce_counter, ); diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dpns/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dpns/v0/mod.rs index 55ffa746720..9efea72c63e 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dpns/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dpns/v0/mod.rs @@ -425,7 +425,7 @@ mod test { let transitions = get_batched_transitions_fixture( [( DocumentTransitionActionType::Create, - vec![(document, document_type, Bytes32::default())], + vec![(document, None, document_type, Bytes32::default())], )], &mut nonce_counter, ); diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs index 4828d923f8d..8efd8227258 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs @@ -969,7 +969,7 @@ impl BatchTransitionInternalTransformerV0 for BatchTransition { StateError::InvalidDocumentRevisionError(InvalidDocumentRevisionError::new( document_id, Some(previous_revision), - transition_revision, + expected_revision, )), )) } diff --git a/packages/wasm-dpp/src/document/document_facade.rs b/packages/wasm-dpp/src/document/document_facade.rs index f713a421f2e..6ee50fb090a 100644 --- a/packages/wasm-dpp/src/document/document_facade.rs +++ b/packages/wasm-dpp/src/document/document_facade.rs @@ -1,8 +1,7 @@ -use std::rc::Rc; -use wasm_bindgen::{prelude::*, JsValue}; - use crate::document::factory::DocumentFactoryWASM; use crate::{DataContractWasm, ExtendedDocumentWasm}; +use std::rc::Rc; +use wasm_bindgen::{prelude::*, JsValue}; use crate::document::state_transition::batch_transition::BatchTransitionWasm; diff --git a/packages/wasm-dpp/src/document/errors/mod.rs b/packages/wasm-dpp/src/document/errors/mod.rs index 7b90ac63dbd..3b3b2018d38 100644 --- a/packages/wasm-dpp/src/document/errors/mod.rs +++ b/packages/wasm-dpp/src/document/errors/mod.rs @@ -6,6 +6,7 @@ use crate::document::errors::invalid_action_error::InvalidActionError; use crate::document::errors::revision_absent_error::RevisionAbsentError; use crate::document::errors::trying_to_delete_immutable_document_error::TryingToDeleteImmutableDocumentError; use crate::document::errors::trying_to_replace_immutable_document_error::TryingToReplaceImmutableDocumentError; +use crate::document::errors::trying_to_transfer_nontransferable_document_error::TryingToTransferNonTransferableDocumentError; pub use document_already_exists_error::*; pub use document_not_provided_error::*; use dpp::document::errors::DocumentError; @@ -32,6 +33,7 @@ mod no_documents_supplied_error; mod revision_absent_error; mod trying_to_delete_immutable_document_error; mod trying_to_replace_immutable_document_error; +mod trying_to_transfer_nontransferable_document_error; pub fn from_document_to_js_error(e: DocumentError) -> JsValue { match e { @@ -78,5 +80,8 @@ pub fn from_document_to_js_error(e: DocumentError) -> JsValue { DocumentError::TryingToDeleteIndelibleDocument { document } => { TryingToDeleteImmutableDocumentError::new((*document).into()).into() } + DocumentError::TryingToTransferNonTransferableDocument { document } => { + TryingToTransferNonTransferableDocumentError::new((*document).into()).into() + } } } diff --git a/packages/wasm-dpp/src/document/errors/trying_to_transfer_nontransferable_document_error.rs b/packages/wasm-dpp/src/document/errors/trying_to_transfer_nontransferable_document_error.rs new file mode 100644 index 00000000000..60ff00f03d4 --- /dev/null +++ b/packages/wasm-dpp/src/document/errors/trying_to_transfer_nontransferable_document_error.rs @@ -0,0 +1,19 @@ +use crate::document::DocumentWasm; +use thiserror::Error; + +use super::*; + +#[wasm_bindgen] +#[derive(Error, Debug)] +#[error("Trying to transfer an non transferable document")] +pub struct TryingToTransferNonTransferableDocumentError { + document: DocumentWasm, +} + +#[wasm_bindgen] +impl TryingToTransferNonTransferableDocumentError { + #[wasm_bindgen(constructor)] + pub fn new(document: DocumentWasm) -> Self { + TryingToTransferNonTransferableDocumentError { document } + } +} diff --git a/packages/wasm-dpp/src/document/factory.rs b/packages/wasm-dpp/src/document/factory.rs index 24b1049505c..962c0a9ab3c 100644 --- a/packages/wasm-dpp/src/document/factory.rs +++ b/packages/wasm-dpp/src/document/factory.rs @@ -9,7 +9,7 @@ use crate::document::errors::InvalidActionNameError; use crate::document::platform_value::Bytes32; use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::document_type::DocumentTypeRef; -use dpp::document::Document; +use dpp::document::{Document, DocumentTransitionParams}; use dpp::prelude::ExtendedDocument; @@ -23,8 +23,8 @@ use crate::{ use dpp::identifier::Identifier; use dpp::state_transition::batch_transition::batched_transition::document_transition_action_type::DocumentTransitionActionType; use dpp::version::PlatformVersion; +use js_sys::Uint8Array; use std::convert::TryFrom; -use std::str::FromStr; #[wasm_bindgen(js_name=DocumentTransitions)] #[derive(Debug, Default)] @@ -134,6 +134,7 @@ impl DocumentFactoryWASM { .unwrap() .parse::() .unwrap(); + nonce_counter.insert((identity_id, contract_id), nonce); }); }); @@ -146,7 +147,7 @@ impl DocumentFactoryWASM { let documents_by_action = extract_documents_by_action(documents)?; for (_, documents) in documents_by_action.iter() { - for document in documents.iter() { + for (document, _) in documents.iter() { if !contract_ids_to_check.contains(&document.data_contract().id()) { return Err(JsValue::from_str( "Document's data contract is not in the nonce counter", @@ -157,15 +158,26 @@ impl DocumentFactoryWASM { let documents: Vec<( DocumentTransitionActionType, - Vec<(Document, DocumentTypeRef, Bytes32)>, + Vec<( + Document, + Option, + DocumentTypeRef, + Bytes32, + )>, )> = documents_by_action .iter() .map(|(action_type, documents)| { - let documents_with_refs: Vec<(Document, DocumentTypeRef, Bytes32)> = documents - .iter() - .map(|extended_document| { + let documents_with_refs: Vec<( + Document, + Option, + DocumentTypeRef, + Bytes32, + )> = documents + .into_iter() + .map(|(extended_document, document_params)| { ( extended_document.document().clone(), + document_params.clone(), extended_document .data_contract() .document_type_for_name(extended_document.document_type_name()) @@ -275,19 +287,37 @@ impl DocumentFactoryWASM { // fn extract_documents_by_action( documents: &JsValue, -) -> Result>, JsValue> { +) -> Result< + HashMap< + DocumentTransitionActionType, + Vec<(ExtendedDocument, Option)>, + >, + JsValue, +> { check_actions(documents)?; - let mut documents_by_action: HashMap> = - Default::default(); + let mut documents_by_action: HashMap< + DocumentTransitionActionType, + Vec<(ExtendedDocument, Option)>, + > = Default::default(); let documents_create = extract_documents_of_action(documents, "create").with_js_error()?; let documents_replace = extract_documents_of_action(documents, "replace").with_js_error()?; let documents_delete = extract_documents_of_action(documents, "delete").with_js_error()?; + let documents_transfer = extract_documents_of_action(documents, "transfer").with_js_error()?; + let documents_update_price = + extract_documents_of_action(documents, "updatePrice").with_js_error()?; + let documents_purchase = extract_documents_of_action(documents, "purchase").with_js_error()?; documents_by_action.insert(DocumentTransitionActionType::Create, documents_create); documents_by_action.insert(DocumentTransitionActionType::Replace, documents_replace); documents_by_action.insert(DocumentTransitionActionType::Delete, documents_delete); + documents_by_action.insert(DocumentTransitionActionType::Transfer, documents_transfer); + documents_by_action.insert( + DocumentTransitionActionType::UpdatePrice, + documents_update_price, + ); + documents_by_action.insert(DocumentTransitionActionType::Purchase, documents_purchase); Ok(documents_by_action) } @@ -317,21 +347,67 @@ fn check_actions(documents: &JsValue) -> Result<(), JsValue> { fn extract_documents_of_action( documents: &JsValue, action: &str, -) -> Result, anyhow::Error> { - let documents_with_action = +) -> Result)>, anyhow::Error> { + let documents_submittable_with_action = js_sys::Reflect::get(documents, &action.to_string().into()).unwrap_or(JsValue::NULL); - if documents_with_action.is_null() || documents_with_action.is_undefined() { + if documents_submittable_with_action.is_null() + || documents_submittable_with_action.is_undefined() + { return Ok(vec![]); } - let documents_array = js_sys::Array::try_from(documents_with_action) + let documents_submittable_array = js_sys::Array::try_from(documents_submittable_with_action) .map_err(|e| anyhow!("property '{}' isn't an array: {}", action, e))?; - documents_array + documents_submittable_array .iter() - .map(|js_document| { - js_document + .map(|js_document_submittable| { + let document = + js_sys::Reflect::get(&js_document_submittable, &"document".into()).unwrap(); + + let params: Option = + js_sys::Reflect::get(&js_document_submittable, &"params".to_string().into()) + .map(|js_value| { + if js_value.is_undefined() || js_value.is_null() { + return None; + } + + let receiver_id = + js_sys::Reflect::get(&js_value, &"receiver".to_string().into()) + .map(|js_value| { + if js_value.is_undefined() || js_value.is_null() { + return None; + } + + let buffer = Uint8Array::new(&js_value); + let bytes = Identifier::from_vec(buffer.to_vec()).unwrap(); + + Some(bytes) + }) + .unwrap(); + + let price: Option = + js_sys::Reflect::get(&js_value, &"price".to_string().into()) + .map(|js_value| { + if js_value.is_undefined() || js_value.is_null() { + return None; + } + + let price = u64::try_from(JsValue::from(&js_value)).unwrap(); + + return Some(price); + }) + .unwrap(); + + Some(DocumentTransitionParams { + receiver: receiver_id, + price: price.map(|e| e.clone()), + }) + }) + .unwrap(); + + document .to_wasm::("ExtendedDocument") .map_err(|e| { anyhow!( @@ -340,7 +416,7 @@ fn extract_documents_of_action( e ) }) - .map(|wasm_doc| wasm_doc.clone().into()) + .map(|wasm_doc| (wasm_doc.clone().into(), params)) }) .collect() } diff --git a/packages/wasm-dpp/test/integration/document/DocumentFacade.spec.js b/packages/wasm-dpp/test/integration/document/DocumentFacade.spec.js index 06f935716f0..7eab01bb7da 100644 --- a/packages/wasm-dpp/test/integration/document/DocumentFacade.spec.js +++ b/packages/wasm-dpp/test/integration/document/DocumentFacade.spec.js @@ -100,7 +100,7 @@ describe('DocumentFacade', () => { const contractId = documents[0].getDataContractId(); const result = dpp.document.createStateTransition({ - create: documents, + create: documents.map((d) => ({ document: d, params: null })), }, { [identityId.toString()]: { [contractId.toString()]: '1', diff --git a/packages/wasm-dpp/test/integration/stateTransition/StateTransitionFacade.spec.js b/packages/wasm-dpp/test/integration/stateTransition/StateTransitionFacade.spec.js index 76e5d81ca47..49bd3acbb0e 100644 --- a/packages/wasm-dpp/test/integration/stateTransition/StateTransitionFacade.spec.js +++ b/packages/wasm-dpp/test/integration/stateTransition/StateTransitionFacade.spec.js @@ -99,7 +99,7 @@ describe('StateTransitionFacade', () => { const documents = await getDocumentsFixture(dataContract); documentsBatchTransition = documentFactory.createStateTransition({ - create: documents, + create: documents.map((d) => ({ document: d, params: null })), }, { [documents[0].getOwnerId().toString()]: { [documents[0].getDataContractId().toString()]: '0', diff --git a/packages/wasm-dpp/test/unit/document/DocumentFactory.spec.js b/packages/wasm-dpp/test/unit/document/DocumentFactory.spec.js index 2ed208c04ee..ace54d99131 100644 --- a/packages/wasm-dpp/test/unit/document/DocumentFactory.spec.js +++ b/packages/wasm-dpp/test/unit/document/DocumentFactory.spec.js @@ -232,7 +232,7 @@ describe('DocumentFactory', () => { it('should throw and error if documents have unknown action', async () => { try { factory.createStateTransition({ - unknown: documents, + unknown: documents.map((d) => ({ document: d, params: null })), }, {}); expect.fail('Error was not thrown'); @@ -274,7 +274,7 @@ describe('DocumentFactory', () => { const expectedDocument = documents[0].toObject(); try { factory.createStateTransition({ - create: documents, + create: documents.map((d) => ({ document: d, params: null })), }); expect.fail('Error was not thrown'); } catch (e) { @@ -290,8 +290,8 @@ describe('DocumentFactory', () => { const identityId = newDocument.getOwnerId(); const stateTransition = factory.createStateTransition({ - create: documents, - replace: [newDocument], + create: documents.map((d) => ({ document: d, params: null })), + replace: [newDocument].map((d) => ({ document: d, params: null })), }, { [identityId.toString()]: { [dataContract.getId().toString()]: '1',