From a062faa8e76902ab5d8058fbc7ca08848b25cb48 Mon Sep 17 00:00:00 2001 From: Vladislav Nikolaenko Date: Mon, 5 Jun 2023 10:44:04 +0300 Subject: [PATCH] NODE-2577: Auto tests for the subscribe request in blockchain updates (#3839) --- .../events/BlockchainUpdatesSpec.scala | 2 +- ...ockchainUpdatesSubscribeInvokeTxSpec.scala | 282 +++++++++ .../BlockchainUpdatesSubscribeSpec.scala | 594 ++++++++++++++++++ .../wavesplatform/events/FakeObserver.scala | 6 - .../wavesplatform/events/MetadataSpec.scala | 2 +- .../wavesplatform/events/WithBUDomain.scala | 13 +- .../fixtures/PrepareInvokeTestData.scala | 117 ++++ .../events/fixtures/WavesTxChecks.scala | 489 ++++++++++++++ .../com/wavesplatform/events/package.scala | 28 + .../wavesplatform/transaction/TxHelpers.scala | 10 + 10 files changed, 1530 insertions(+), 13 deletions(-) create mode 100644 grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeInvokeTxSpec.scala create mode 100644 grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeSpec.scala create mode 100644 grpc-server/src/test/scala/com/wavesplatform/events/fixtures/PrepareInvokeTestData.scala create mode 100644 grpc-server/src/test/scala/com/wavesplatform/events/fixtures/WavesTxChecks.scala create mode 100644 grpc-server/src/test/scala/com/wavesplatform/events/package.scala diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala index ff36db47b72..a92cb5c25fb 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala @@ -888,7 +888,7 @@ class BlockchainUpdatesSpec extends FreeSpec with WithBUDomain with ScalaFutures startRead.lock() - val subscription = Future(repo.createSubscriptionObserver(SubscribeRequest.of(1, toHeight))) + val subscription = Future(repo.createFakeObserver(SubscribeRequest.of(1, toHeight))) appendExtraBlocks(d) diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeInvokeTxSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeInvokeTxSpec.scala new file mode 100644 index 00000000000..2527c895d10 --- /dev/null +++ b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeInvokeTxSpec.scala @@ -0,0 +1,282 @@ +package com.wavesplatform.events + +import com.wavesplatform.account.{Address, SeedKeyPair} +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.* +import com.wavesplatform.db.WithState.AddrWithBalance +import com.wavesplatform.events.StateUpdate.LeaseUpdate.LeaseStatus +import com.wavesplatform.events.fixtures.PrepareInvokeTestData.* +import com.wavesplatform.events.fixtures.WavesTxChecks.* +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BYTESTR, CONST_LONG, CONST_STRING, EXPR} +import com.wavesplatform.protobuf.transaction.PBAmounts.toVanillaAssetId +import com.wavesplatform.events.protobuf.BlockchainUpdated as PBBlockchainUpdated +import com.wavesplatform.events.protobuf.BlockchainUpdated.Append +import com.wavesplatform.settings.WavesSettings +import com.wavesplatform.state.{BinaryDataEntry, BooleanDataEntry, DataEntry, EmptyDataEntry, IntegerDataEntry, StringDataEntry} +import com.wavesplatform.test.{FreeSpec, NumericExt} +import com.wavesplatform.transaction.Asset.Waves +import com.wavesplatform.transaction.{TxHelpers, TxNonNegativeAmount} +import com.wavesplatform.transaction.TxHelpers.{secondAddress, secondSigner} +import com.wavesplatform.transaction.smart.SetScriptTransaction +import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer +import org.scalatest.concurrent.ScalaFutures + +class BlockchainUpdatesSubscribeInvokeTxSpec extends FreeSpec with WithBUDomain with ScalaFutures { + val currentSettings: WavesSettings = DomainPresets.RideV6 + val dAppAccount: SeedKeyPair = TxHelpers.signer(2) + val sender: SeedKeyPair = TxHelpers.signer(3) + val senderAddress: Address = sender.toAddress + val dAppAddress: Address = dAppAccount.toAddress + val dAppBalanceBefore: Long = 10.waves + val senderWavesBalanceBefore: Long = 8.waves + + "BU-31. Invoke have to return correct data for subscribe" in { + for (libVersion <- 5 to 6) { + val issue = TxHelpers.issue(dAppAccount) + val setScript = TxHelpers.setScript(dAppAccount, TxHelpers.script(invokeAssetScript(libVersion))) + val asset = issue.asset + val assetByteStr = CONST_BYTESTR(asset.id).explicitGet() + val addressByteStr = CONST_BYTESTR(ByteStr.apply(senderAddress.bytes)).explicitGet() + val args: Seq[EXPR] = Seq(assetByteStr, addressByteStr) + val invoke = TxHelpers.invoke(dAppAddress, Some("setData"), args, Seq.empty, sender, fee = 100500000L) + val dAppInvokeIssueBalance: Long = issueData.apply("amount").toString.toLong - scriptTransferIssueAssetNum + val dAppBalanceBeforeInvoke: Long = dAppBalanceBefore - issue.fee.value - setScript.fee.value + val dAppWavesBalanceAfterTx: Long = dAppBalanceBeforeInvoke - scriptTransferUnitNum + val dAppAssetBalanceAfterTx: Long = issue.quantity.value - burnNum - scriptTransferAssetNum + reissueNum + val senderWavesBalanceAfterTx: Long = senderWavesBalanceBefore - invoke.fee.value + scriptTransferUnitNum + val dataEntry: Seq[DataEntry[?]] = Seq[DataEntry[?]]( + EmptyDataEntry("int"), + BinaryDataEntry("byte", asset.id), + BooleanDataEntry("bool", value = true), + StringDataEntry("str", "test_string") + ) + val actualData = Seq( + ("int", dataMap.apply("intVal")), + ("byte", asset.id.arr), + ("bool", true), + ("str", dataMap.apply("stringVal")), + ("int", None) + ) + + withGenerateSubscription( + settings = currentSettings, + balances = Seq( + AddrWithBalance(dAppAddress, dAppBalanceBefore), + AddrWithBalance(senderAddress, senderWavesBalanceBefore) + ) + ) { d => + d.appendBlock(setScript) + d.appendBlock(issue) + d.appendMicroBlock(invoke) + } { updates => + val append = updates(3).append + val transactionMetadata = append.transactionsMetadata + val invokeScript = transactionMetadata.head.getInvokeScript + val arguments = invokeScript.arguments + val result = invokeScript.result.get + val dataEntries = append.transactionStateUpdates.head.dataEntries + val invokeIssueAsset = toVanillaAssetId(result.issues.head.assetId) + val invokeLeaseId = result.leases.head.leaseId.toByteArray + val assetDetails = append.transactionStateUpdates.head.assets + val expectedValues = List(asset.id.arr, senderAddress.bytes) + val actualArguments = List( + arguments.head.value.binaryValue.get.toByteArray, + arguments.apply(1).value.binaryValue.get.toByteArray + ) + checkInvokeTransaction(append.transactionIds.head, append.transactionAt(0), invoke, dAppAddress.publicKeyHash) + checkInvokeBaseTransactionMetadata(transactionMetadata, invoke) + checkArguments(expectedValues, actualArguments) + checkInvokeScriptResultData(result.data, actualData) + checkInvokeScriptResultIssues(result.issues.head, issueData) + checkInvokeScriptResultTransfers(result.transfers.head, senderAddress, scriptTransferAssetNum, asset) + checkInvokeScriptResultTransfers(result.transfers.apply(1), senderAddress, scriptTransferIssueAssetNum, invokeIssueAsset) + checkInvokeScriptResultTransfers(result.transfers.apply(2), senderAddress, scriptTransferUnitNum, Waves) + checkInvokeScriptResultReissue(result.reissues.head, asset, reissueNum, reissuable = true) + checkInvokeScriptResultBurn(result.burns.head, asset, burnNum) + checkInvokeScriptResultSponsorFee(result.sponsorFees.head, asset, sponsorFeeAssetNum) + checkInvokeScriptResultSponsorFee(result.sponsorFees.apply(1), invokeIssueAsset, sponsorFeeIssueAssetNum) + checkInvokeScriptResultLease(result.leases.head, senderAddress.publicKeyHash, leaseNum) + checkInvokeScriptResultLeaseCancel(result.leaseCancels.head, invokeLeaseId) + checkBalances( + append.transactionStateUpdates.head.balances, + Map( + (senderAddress, Waves) -> (senderWavesBalanceBefore, senderWavesBalanceAfterTx), + (senderAddress, asset) -> (0, scriptTransferAssetNum), + (senderAddress, invokeIssueAsset) -> (0, scriptTransferIssueAssetNum), + (dAppAddress, Waves) -> (dAppBalanceBeforeInvoke, dAppWavesBalanceAfterTx), + (dAppAddress, invokeIssueAsset) -> (0, dAppInvokeIssueBalance), + (dAppAddress, asset) -> (issue.quantity.value, dAppAssetBalanceAfterTx) + ) + ) + checkDataEntriesStateUpdate(dataEntries, dataEntry, dAppAddress.bytes) + checkAssetsStateUpdates(assetDetails.head.after, issueData, invokeIssueAsset, dAppAccount.publicKey.arr) + checkAssetsStateUpdates(assetDetails.apply(1).before, issue, isNft = false, issue.quantity.value) + checkAssetsStateUpdates(assetDetails.apply(1).after, issue, isNft = false, dAppAssetBalanceAfterTx + scriptTransferAssetNum) + checkIndividualLeases( + append.transactionStateUpdates.head.individualLeases, + Map( + ( + LeaseStatus.Inactive, + leaseNum + ) -> (invokeLeaseId, dAppAccount.publicKey.arr, senderAddress.bytes, invoke.id.value().arr) + ) + ) + } + } + } + + "Double nesting call tests" - { + val assetDappAccount: SeedKeyPair = TxHelpers.signer(4) + val assetDappAddress: Address = assetDappAccount.toAddress + val invokerDappAccount: SeedKeyPair = TxHelpers.signer(5) + val invokerDappAddress: Address = invokerDappAccount.toAddress + val issue = TxHelpers.issue(assetDappAccount) + val asset = issue.asset + val assetTransferAmount: Long = 200000000L + val massTx = TxHelpers.massTransfer( + assetDappAccount, + Seq( + ParsedTransfer(dAppAddress, TxNonNegativeAmount.unsafeFrom(assetTransferAmount)), + ParsedTransfer(secondAddress, TxNonNegativeAmount.unsafeFrom(assetTransferAmount)), + ParsedTransfer(invokerDappAddress, TxNonNegativeAmount.unsafeFrom(assetTransferAmount)) + ), + asset, + fee = 500000 + ) + val args: Seq[EXPR] = + Seq( + CONST_BYTESTR(ByteStr.apply(secondAddress.bytes)).explicitGet(), + CONST_BYTESTR(ByteStr.apply(assetDappAddress.bytes)).explicitGet(), + CONST_LONG(scriptTransferUnitNum), + CONST_STRING(bar).explicitGet(), + CONST_BYTESTR(asset.id).explicitGet() + ) + val actualData = Seq(("bar", scriptTransferUnitNum * 2)) + val invoke = TxHelpers.invoke(dAppAddress, Some("foo"), args, Seq.empty, invokerDappAccount, fee = 100500000L) + val invokerDappBalance: Long = 4.waves + val secondAddressBalance: Long = 8.waves + val assetDappBalance: Long = 12.waves + + "BU-77. doubles nested i.caller. Invoke have to return correct data for subscribe" in { + for (libVersion <- 5 to 6) { + val scriptTransferWavesSum = scriptTransferUnitNum * 2 + val mainDAppTx = TxHelpers.setScript(dAppAccount, TxHelpers.script(mainDAppScript(libVersion))) + val nestedDAppTx = TxHelpers.setScript(secondSigner, TxHelpers.script(nestedDAppScript("i.caller", libVersion))) + val doubleNestedDAppTx = TxHelpers.setScript(assetDappAccount, TxHelpers.script(doubleNestedDAppScript("i.caller", libVersion))) + val expectDataEntries: Seq[DataEntry[?]] = Seq[DataEntry[?]](IntegerDataEntry(bar, scriptTransferWavesSum)) + val secondAddressWavesBalanceBefore = secondAddressBalance - nestedDAppTx.fee.value + val secondAddressWavesBalanceAfter = secondAddressWavesBalanceBefore + scriptTransferUnitNum + val assetDappAddressWavesBalanceBefore = assetDappBalance - issue.fee.value - massTx.fee.value - doubleNestedDAppTx.fee.value + val assetDappAddressWavesBalanceAfter = assetDappAddressWavesBalanceBefore - scriptTransferUnitNum + val invokerDappAddressWavesBalance = invokerDappBalance - invoke.fee.value + val secondAddressAssetBalance = assetTransferAmount - scriptTransferAssetNum + paymentNum + val dAppAddressAssetBalance = assetTransferAmount + scriptTransferAssetNum - paymentNum + + addedBlocksAndSubscribe(mainDAppTx, nestedDAppTx, doubleNestedDAppTx) { updates => + val actualDataEntries = updates(2).getAppend.transactionStateUpdates.head.dataEntries + checkInvokeDoubleNestedBlockchainUpdates(updates(2).getAppend, dAppAddress, secondAddress) + checkBalances( + updates(2).getAppend.transactionStateUpdates.head.balances, + Map( + (secondAddress, Waves) -> (secondAddressWavesBalanceBefore, secondAddressWavesBalanceAfter), + (secondAddress, asset) -> (assetTransferAmount, secondAddressAssetBalance), + (dAppAddress, asset) -> (assetTransferAmount, dAppAddressAssetBalance), + (assetDappAddress, Waves) -> (assetDappAddressWavesBalanceBefore, assetDappAddressWavesBalanceAfter), + (invokerDappAddress, Waves) -> (invokerDappBalance, invokerDappAddressWavesBalance) + ) + ) + checkDataEntriesStateUpdate(actualDataEntries, expectDataEntries, dAppAddress.bytes) + } + } + } + + "BU-39. double nested i.originCaller. Invoke have to return correct data for subscribe" in { + for (libVersion <- 5 to 6) { + val mainDAppTx = TxHelpers.setScript(dAppAccount, TxHelpers.script(mainDAppScript(libVersion))) + val nestedDAppTx = TxHelpers.setScript(secondSigner, TxHelpers.script(nestedDAppScript("i.originCaller", libVersion))) + val doubleNestedDAppTx = TxHelpers.setScript(assetDappAccount, TxHelpers.script(doubleNestedDAppScript("i.originCaller", libVersion))) + val expectDataEntries: Seq[DataEntry[?]] = Seq[DataEntry[?]](IntegerDataEntry(bar, scriptTransferUnitNum * 2)) + val assetDappAddressWavesBalanceBefore = assetDappBalance - issue.fee.value - massTx.fee.value - doubleNestedDAppTx.fee.value + val assetDappAddressWavesBalanceAfter = assetDappAddressWavesBalanceBefore - scriptTransferUnitNum + val invokerDappAddressWavesBalance = invokerDappBalance - invoke.fee.value + scriptTransferUnitNum + val invokerDappAddressAssetBalance = assetTransferAmount + scriptTransferAssetNum + val secondAddressAssetBalance = assetTransferAmount - scriptTransferAssetNum + paymentNum + val dAppAddressAssetBalance = assetTransferAmount - paymentNum + + addedBlocksAndSubscribe(mainDAppTx, nestedDAppTx, doubleNestedDAppTx) { updates => + val actualDataEntries = updates(2).getAppend.transactionStateUpdates.head.dataEntries + checkInvokeDoubleNestedBlockchainUpdates(updates(2).getAppend, invokerDappAddress, invokerDappAddress) + checkBalances( + updates(2).getAppend.transactionStateUpdates.head.balances, + Map( + (invokerDappAddress, asset) -> (assetTransferAmount, invokerDappAddressAssetBalance), + (secondAddress, asset) -> (assetTransferAmount, secondAddressAssetBalance), + (dAppAddress, asset) -> (assetTransferAmount, dAppAddressAssetBalance), + (assetDappAddress, Waves) -> (assetDappAddressWavesBalanceBefore, assetDappAddressWavesBalanceAfter), + (invokerDappAddress, Waves) -> (invokerDappBalance, invokerDappAddressWavesBalance) + ) + ) + checkDataEntriesStateUpdate(actualDataEntries, expectDataEntries, dAppAddress.bytes) + } + } + } + + def addedBlocksAndSubscribe( + mainDAppTx: SetScriptTransaction, + nestedDAppTx: SetScriptTransaction, + doubleNestedDAppTx: SetScriptTransaction + )(f: Seq[PBBlockchainUpdated] => Unit): Unit = { + withGenerateSubscription( + settings = currentSettings, + balances = Seq( + AddrWithBalance(dAppAddress, dAppBalanceBefore), + AddrWithBalance(secondAddress, secondAddressBalance), + AddrWithBalance(invokerDappAddress, invokerDappBalance), + AddrWithBalance(assetDappAddress, assetDappBalance) + ) + ) { d => + d.appendBlock(issue, massTx, mainDAppTx, nestedDAppTx, doubleNestedDAppTx) + d.appendMicroBlock(invoke) + } { updates => + f(updates) + } + } + + def checkInvokeDoubleNestedBlockchainUpdates( + append: Append, + nestedTransferAddress: Address, + doubleNestedTransferAddress: Address + ): Unit = { + val arguments = append.transactionsMetadata.head.getInvokeScript.arguments + val result = append.transactionsMetadata.head.getInvokeScript.result.get + val invokes = result.invokes.head + val invokesStateChangeInvoke = invokes.stateChanges.get.invokes.head + val expectedValues: List[Any] = List(secondAddress.bytes, assetDappAddress.bytes, scriptTransferUnitNum, bar, asset.id.arr) + val actualArguments: List[Any] = List( + arguments.head.value.binaryValue.get.toByteArray, + arguments(1).value.binaryValue.get.toByteArray, + arguments(2).value.integerValue.get, + arguments(3).value.stringValue.get, + arguments(4).value.binaryValue.get.toByteArray + ) + checkInvokeTransaction(append.transactionIds.head, append.transactionAt(0), invoke, dAppAddress.publicKeyHash) + checkInvokeBaseTransactionMetadata(append.transactionsMetadata, invoke) + checkArguments(expectedValues, actualArguments) + checkInvokeScriptResultData(result.data, actualData) + checkInvokeScriptBaseInvokes(invokes, secondAddress, bar) + checkInvokeScriptInvokesArgs(invokes.call.get.args.head, scriptTransferUnitNum) + checkInvokeScriptInvokesArgs(invokes.call.get.args.apply(1), asset.id.arr) + checkInvokeScriptInvokesArgs(invokes.call.get.args.apply(2), assetDappAddress.bytes) + checkInvokeScriptInvokesPayments(invokes.payments.head, asset, paymentNum) + checkInvokeScriptResultTransfers(invokes.stateChanges.get.transfers.head, nestedTransferAddress, scriptTransferAssetNum, asset) + checkInvokeScriptBaseInvokes(invokesStateChangeInvoke, assetDappAddress, baz) + checkInvokeScriptInvokesArgs(invokesStateChangeInvoke.call.get.args.head, scriptTransferUnitNum) + checkInvokeScriptResultTransfers( + invokesStateChangeInvoke.stateChanges.get.transfers.head, + doubleNestedTransferAddress, + scriptTransferUnitNum, + Waves + ) + } + } +} diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeSpec.scala new file mode 100644 index 00000000000..deba082ae33 --- /dev/null +++ b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeSpec.scala @@ -0,0 +1,594 @@ +package com.wavesplatform.events + +import com.wavesplatform.TestValues.fee +import com.wavesplatform.account.{Address, SeedKeyPair} +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.db.WithState.AddrWithBalance +import com.wavesplatform.events.StateUpdate.LeaseUpdate.LeaseStatus +import com.wavesplatform.events.fixtures.WavesTxChecks.{checkExchange, *} +import com.wavesplatform.events.protobuf.BlockchainUpdated as PBBlockchainUpdated +import com.wavesplatform.events.protobuf.BlockchainUpdated.Append +import com.wavesplatform.features.BlockchainFeatures +import com.wavesplatform.lang.script.Script +import com.wavesplatform.settings.WavesSettings +import com.wavesplatform.state.{BinaryDataEntry, BooleanDataEntry, IntegerDataEntry, StringDataEntry} +import com.wavesplatform.test.* +import com.wavesplatform.test.DomainPresets.* +import com.wavesplatform.transaction.Asset.Waves +import com.wavesplatform.transaction.assets.IssueTransaction +import com.wavesplatform.transaction.assets.exchange.{ExchangeTransaction, Order, OrderType} +import com.wavesplatform.transaction.{Asset, TxHelpers, TxVersion} +import org.scalatest.concurrent.ScalaFutures + +class BlockchainUpdatesSubscribeSpec extends FreeSpec with WithBUDomain with ScalaFutures { + val currentSettings: WavesSettings = DomainPresets.RideV6 + val customFee: Long = 5234000L + val customAssetIssueFee = 234560000L + val sender: SeedKeyPair = TxHelpers.signer(12) + val senderAddress: Address = sender.toAddress + val senderBalanceBefore: Long = 20.waves + val testScript: Script = TxHelpers.script(s"""{-# STDLIB_VERSION 6 #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |@Verifier(tx) + |func verify () = match(tx) { + | case _ => + | if ( + | ${(1 to 9).map(_ => "sigVerify(base58'', base58'', base58'')").mkString(" || \n")} + | ) then true else true + |}""".stripMargin) + + "BlockchainUpdates subscribe tests" - { + "BU-1. Return correct data for alias" in { + val aliasTx = TxHelpers.createAlias("test", sender, fee = customFee) + val senderBalanceAfterTx: Long = senderBalanceBefore - aliasTx.fee.value + + withGenerateSubscription( + settings = currentSettings, + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + )(_.appendMicroBlock(aliasTx)) { updates => + val append = updates(1).append + checkCreateAlias(append.transactionIds.head, append.transactionAt(0), aliasTx) + checkBalances( + append.transactionStateUpdates.head.balances, + Map((senderAddress, Waves) -> (senderBalanceBefore, senderBalanceAfterTx)) + ) + } + } + + "BU-28. Return correct data for transfer" in { + val amount: Long = 1000L + val transferSenderBalanceAfter = senderBalanceBefore - customFee - amount + val transferRecipient = TxHelpers.signer(123) + val recipientAddress = transferRecipient.toAddress + val transferRecipientBalanceBefore = 1.waves + val transferRecipientBalanceAfter = transferRecipientBalanceBefore + amount + val transferTx = TxHelpers.transfer(sender, recipientAddress, amount, Waves, customFee) + + withGenerateSubscription( + settings = currentSettings, + balances = Seq( + AddrWithBalance(senderAddress, senderBalanceBefore), + AddrWithBalance(transferRecipient.toAddress, transferRecipientBalanceBefore) + ) + )(_.appendMicroBlock(transferTx)) { updates => + val append = updates(1).append + checkTransfer(append.transactionIds.head, append.transactionAt(0), transferTx, recipientAddress.publicKeyHash) + checkBalances( + append.transactionStateUpdates.head.balances, + Map( + (senderAddress, Waves) -> (senderBalanceBefore, transferSenderBalanceAfter), + (recipientAddress, Waves) -> (transferRecipientBalanceBefore, transferRecipientBalanceAfter) + ) + ) + } + } + + "BU-9. Return correct data for issue" in { + val script = Option(TxHelpers.script("true")) + val amount: Long = 599000 + val decimals: Byte = 8 + val name: String = "Test_asset" + val description: String = name + "|_|_|_|_|_|" + 5380000 + val issue = TxHelpers.issue(sender, amount, decimals, name, description, customAssetIssueFee, script) + val issueScript = issue.script.get.bytes.value().arr + + withGenerateSubscription( + settings = currentSettings, + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + )(_.appendMicroBlock(issue)) { updates => + val append = updates(1).append + val assetDetails = append.transactionStateUpdates.head.assets.head + + checkIssue(append.transactionIds.head, append.transactionAt(0), issue) + checkBalances( + append.transactionStateUpdates.head.balances, + Map( + (senderAddress, Waves) -> (senderBalanceBefore, senderBalanceBefore - customAssetIssueFee), + (senderAddress, issue.asset) -> (0, amount) + ) + ) + checkAssetsStateUpdates(assetDetails.after, issue, isNft = false, issue.quantity.value) + checkAssetsScriptStateUpdates(assetDetails.after.get.scriptInfo, issueScript) + } + } + + "BU-11. Return correct data for issue NFT" in { + val name: String = "Nft_test_asset" + val description: String = name + "_OVER_9000" + val issueNftTx = IssueTransaction + .selfSigned( + TxVersion.V3, + sender, + name, + description, + quantity = 1, + decimals = 0, + reissuable = false, + script = None, + 0.001.waves, + System.currentTimeMillis() + ) + .explicitGet() + + withGenerateSubscription( + settings = currentSettings.addFeatures(BlockchainFeatures.ReduceNFTFee), + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + )(_.appendMicroBlock(issueNftTx)) { updates => + val append = updates(1).append + val assetDetails = append.transactionStateUpdates.head.assets.head + + checkIssue(append.transactionIds.head, append.transactionAt(0), issueNftTx) + checkBalances( + append.transactionStateUpdates.head.balances, + Map( + (senderAddress, Waves) -> (senderBalanceBefore, senderBalanceBefore - 0.001.waves), + (senderAddress, issueNftTx.asset) -> (0, 1) + ) + ) + checkAssetsStateUpdates(assetDetails.after, issueNftTx, isNft = true, issueNftTx.quantity.value) + } + } + + "BU-19. Return correct data for reissue" in { + val amount: Long = 9000000 + val amountReissue: Long = 7500000 + val issue = TxHelpers.issue(sender, amount) + val reissueTx = TxHelpers.reissue(issue.asset, sender, amountReissue, reissuable = false, customAssetIssueFee) + val quantityAfterReissue = amount + amountReissue + val senderBalanceBeforeReissue = senderBalanceBefore - issue.fee.value + val senderBalanceAfterReissue = senderBalanceBeforeReissue - reissueTx.fee.value + + withGenerateSubscription( + settings = currentSettings, + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + ) { d => + d.appendBlock(issue) + d.appendMicroBlock(reissueTx) + } { updates => + val append = updates(2).append + val assetDetails = append.transactionStateUpdates.head.assets.head + + checkReissue(append.transactionIds.head, append.transactionAt(0), reissueTx) + checkBalances( + append.transactionStateUpdates.head.balances, + Map( + (senderAddress, Waves) -> (senderBalanceBeforeReissue, senderBalanceAfterReissue), + (senderAddress, reissueTx.asset) -> (amount, quantityAfterReissue) + ) + ) + checkAssetsStateUpdates(assetDetails.before, issue, isNft = false, issue.quantity.value) + checkAssetsStateUpdates(assetDetails.after, reissueTx, isNft = false, quantityAfterReissue) + } + } + + "BU-4. Return correct data for burn" in { + val amount: Long = 6000000 + val amountBurn: Long = 5000000 + val issue = TxHelpers.issue(sender, amount) + val burnTx = TxHelpers.burn(issue.asset, amountBurn, sender, customAssetIssueFee) + val amountAfterTx = amount - amountBurn + val senderBalanceBeforeBurn = senderBalanceBefore - issue.fee.value + val senderBalanceAfterBurn = senderBalanceBeforeBurn - burnTx.fee.value + + withGenerateSubscription( + settings = currentSettings, + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + ) { d => + d.appendBlock(issue) + d.appendMicroBlock(burnTx) + } { updates => + val append = updates(2).append + val assetDetails = append.transactionStateUpdates.head.assets.head + checkBurn(append.transactionIds.head, append.transactionAt(0), burnTx) + checkBalances( + append.transactionStateUpdates.head.balances, + Map( + (senderAddress, Waves) -> (senderBalanceBeforeBurn, senderBalanceAfterBurn), + (senderAddress, burnTx.asset) -> (amount, amountAfterTx) + ) + ) + checkAssetsStateUpdates(assetDetails.before, issue, isNft = false, issue.quantity.value) + checkAssetsStateUpdates(assetDetails.after, burnTx, isNft = false, amountAfterTx) + } + } + + "Exchange transaction subscription tests" - { + val buyer = TxHelpers.signer(58) + val seller = TxHelpers.signer(189) + val buyerBalanceBefore = 4.waves + val buyerBalanceBeforeExchange = 3.waves + val sellerBalanceBefore = 4.waves + val sellerBalanceBeforeExchange = 3.waves + val priceAsset = TxHelpers.issue(buyer, 2000000000, 2) + val priceAssetQuantity = priceAsset.quantity.value + val amountAsset = TxHelpers.issue(seller, 1000000000, 6) + val amountAssetQuantity = amountAsset.quantity.value + + "BU-6. Return correct data for order V3, exchange V2" in { + val order1 = TxHelpers.order( + OrderType.BUY, + amountAsset.asset, + priceAsset.asset, + Waves, + 50000L, + 400000000L, + fee = customFee, + sender = buyer, + matcher = buyer, + version = Order.V3 + ) + val order2 = TxHelpers.order( + OrderType.SELL, + amountAsset.asset, + priceAsset.asset, + Waves, + amount = 50000L, + price = 400000000L, + fee = customFee, + sender = seller, + matcher = buyer, + version = Order.V3 + ) + val exchangedAssets = order1.price.value * order1.amount.value / 100000000 + val exchangeTx = TxHelpers.exchangeFromOrders(order1, order2, buyer, version = TxVersion.V2) + addedBlocksAndSubscribe(exchangeTx) { updated => + checkingSubscribeFields(updated(3).getAppend, exchangeTx, exchangedAssets, order1.amount.value) + } + } + + "BU-120. Return correct data for order V4, exchange V3" in { + val order1 = TxHelpers.order( + OrderType.BUY, + amountAsset.asset, + priceAsset.asset, + Waves, + 50000L, + 400000000L, + fee = customFee, + sender = buyer, + matcher = buyer, + version = Order.V4 + ) + val order2 = TxHelpers.order( + OrderType.SELL, + amountAsset.asset, + priceAsset.asset, + Waves, + amount = 50000L, + price = 400000000L, + fee = customFee, + sender = seller, + matcher = buyer, + version = Order.V4 + ) + val exchangedAssets = order1.price.value / 2 / 10000000 + val exchangeTx = TxHelpers.exchangeFromOrders(order1, order2, buyer, version = TxVersion.V3) + addedBlocksAndSubscribe(exchangeTx) { updated => + checkingSubscribeFields(updated(3).getAppend, exchangeTx, exchangedAssets, order1.amount.value) + } + } + + def addedBlocksAndSubscribe(exchangeTx: ExchangeTransaction)(f: Seq[PBBlockchainUpdated] => Unit): Unit = { + withGenerateSubscription( + settings = currentSettings, + balances = Seq( + AddrWithBalance(buyer.toAddress, buyerBalanceBefore), + AddrWithBalance(seller.toAddress, sellerBalanceBefore) + ) + ) { d => + d.appendBlock(priceAsset) + d.appendBlock(amountAsset) + d.appendMicroBlock(exchangeTx) + }(f) + } + + def checkingSubscribeFields(append: Append, exchangeTx: ExchangeTransaction, exchangedAssets: Long, orderAmount: Long): Unit = { + checkExchange(append.transactionIds.head, append.transactionAt(0), exchangeTx) + checkBalances( + append.transactionStateUpdates.head.balances, + Map( + (buyer.toAddress, Waves) -> (buyerBalanceBeforeExchange, buyerBalanceBeforeExchange - fee + customFee), + (seller.toAddress, priceAsset.asset) -> (0, exchangedAssets), + (buyer.toAddress, amountAsset.asset) -> (0, orderAmount), + (seller.toAddress, Waves) -> (sellerBalanceBeforeExchange, sellerBalanceBeforeExchange - customFee), + (buyer.toAddress, priceAsset.asset) -> (priceAssetQuantity, priceAssetQuantity - exchangedAssets), + (seller.toAddress, amountAsset.asset) -> (amountAssetQuantity, amountAssetQuantity - orderAmount) + ) + ) + } + } + + "BU-12. Return correct data for lease" in { + val recipient = TxHelpers.signer(123) + val recipientAddress = recipient.toAddress + val amount = 5.waves + val lease = TxHelpers.lease(sender, recipientAddress, amount, customFee) + val leaseId = lease.id.value().arr + + withGenerateSubscription( + settings = currentSettings, + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + )(_.appendMicroBlock(lease)) { updates => + val append = updates(1).append + checkLease(append.transactionIds.head, append.transactionAt(0), lease, recipientAddress.publicKeyHash) + checkBalances( + append.transactionStateUpdates.head.balances, + Map( + (senderAddress, Waves) -> (senderBalanceBefore, senderBalanceBefore - customFee) + ) + ) + checkLeasingForAddress( + append.transactionStateUpdates.head.leasingForAddress, + Map( + (senderAddress, 0L, amount) -> (0L, 0L), + (recipientAddress, amount, 0L) -> (0L, 0L) + ) + ) + checkIndividualLeases( + append.transactionStateUpdates.head.individualLeases, + Map( + (LeaseStatus.Active, amount) -> (leaseId, lease.sender.arr, lease.recipient.bytes, leaseId) + ) + ) + } + } + + "BU-14. Return correct data for lease cancel" in { + val recipient = TxHelpers.signer(123) + val recipientAddress = recipient.toAddress + val amount = 5.waves + val lease = TxHelpers.lease(sender, recipientAddress, amount, customFee) + val leaseCancel = TxHelpers.leaseCancel(lease.id.value(), sender, customFee) + val leaseId = leaseCancel.leaseId.arr + val senderBalanceBeforeTx = senderBalanceBefore - lease.fee.value + val senderBalanceAfterTx = senderBalanceBeforeTx - leaseCancel.fee.value + + withGenerateSubscription( + settings = currentSettings, + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + ) { d => + d.appendBlock(lease) + d.appendMicroBlock(leaseCancel) + } { updates => + val append = updates(2).append + checkLeaseCancel(append.transactionIds.head, append.transactionAt(0), leaseCancel) + checkBalances( + append.transactionStateUpdates.head.balances, + Map( + (senderAddress, Waves) -> (senderBalanceBeforeTx, senderBalanceAfterTx) + ) + ) + checkLeasingForAddress( + append.transactionStateUpdates.head.leasingForAddress, + Map( + (senderAddress, 0L, 0L) -> (0L, amount), + (recipientAddress, 0L, 0L) -> (amount, 0L) + ) + ) + checkIndividualLeases( + append.transactionStateUpdates.head.individualLeases, + Map( + (LeaseStatus.Inactive, amount) -> (leaseId, lease.sender.arr, lease.recipient.bytes, leaseId) + ) + ) + } + } + + "BU-16. Return correct data for massTransfer" in { + val massTransferFee = fee * 6 + val transferAmount = 500000L + val recipients = TxHelpers.accountSeqGenerator(100, transferAmount) + val issue = TxHelpers.issue(sender, 1000000000L) + val issuedAsset: Asset = Asset.fromCompatId(issue.asset.compatId) + val massTransfer = TxHelpers.massTransfer(sender, recipients, issuedAsset, massTransferFee) + val senderBalanceBeforeTx = senderBalanceBefore - issue.fee.value + val senderBalanceAfterTx = senderBalanceBeforeTx - massTransfer.fee.value + val senderAssetBalanceAfterTx = issue.quantity.value - transferAmount * recipients.size + + withGenerateSubscription( + settings = currentSettings, + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + ) { d => + d.appendBlock(issue) + d.appendMicroBlock(massTransfer) + } { updates => + val balancesMap = Map( + (senderAddress, Waves) -> (senderBalanceBeforeTx, senderBalanceAfterTx), + (senderAddress, issuedAsset) -> (issue.quantity.value, senderAssetBalanceAfterTx) + ) ++ recipients.map(r => (Address.fromBytes(r.address.bytes).explicitGet(), issuedAsset) -> (0L, transferAmount)).toMap + val append = updates(2).append + checkMassTransfer( + append.transactionIds.head, + append.transactionAt(0), + massTransfer, + recipients.map(r => + r.address match { + case address: Address => Some(address.publicKeyHash).get + case _ => fail("not an address") + } + ) + ) + checkMassTransferBalances(append.transactionStateUpdates.head.balances, balancesMap) + } + } + + "BU-5. Return correct data for data" in { + val integerDataEntry = IntegerDataEntry.apply("Integer", 3550000L) + val booleanDataEntry = BooleanDataEntry.apply("Boolean", value = true) + val stringDataEntry = StringDataEntry.apply("String", "test") + val binaryDataEntry = BinaryDataEntry.apply("Binary", ByteStr.apply(senderAddress.bytes)) + val entries = Seq(booleanDataEntry, integerDataEntry, stringDataEntry, binaryDataEntry) + val dataTx = TxHelpers.data(sender, entries, customFee, TxVersion.V2) + + withGenerateSubscription( + settings = currentSettings.addFeatures(BlockchainFeatures.SmartAccounts), + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + )(_.appendMicroBlock(dataTx)) { updates => + val append = updates(1).append + val txUpdates = append.transactionStateUpdates.head + checkDataTransaction(append.transactionIds.head, append.transactionAt(0), dataTx) + checkBalances( + txUpdates.balances, + Map((senderAddress, Waves) -> (senderBalanceBefore, senderBalanceBefore - customFee)) + ) + checkDataEntriesStateUpdate(txUpdates.dataEntries, dataTx.data, senderAddress.bytes) + } + } + + "BU-21. Return correct data for setScript" in { + val setScript = TxHelpers.setScript(sender, testScript, customFee) + + withGenerateSubscription( + settings = currentSettings.addFeatures(BlockchainFeatures.SmartAccounts), + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + )(_.appendMicroBlock(setScript)) { updates => + val append = updates(1).append + val txUpdates = append.transactionStateUpdates.head + checkSetScriptTransaction(append.transactionIds.head, append.transactionAt(0), setScript) + checkBalances( + txUpdates.balances, + Map((senderAddress, Waves) -> (senderBalanceBefore, senderBalanceBefore - customFee)) + ) + checkSetScriptStateUpdate(txUpdates.scripts.head, setScript) + } + } + + "Return correct data for sponsorFee" - { + val sponsorshipFee = Option.apply(3950000L) + val issue = TxHelpers.issue(sender, 99900000L) + val sponsorFee = TxHelpers.sponsor(issue.asset, sponsorshipFee, sender) + val sponsorFeeCancel = TxHelpers.sponsor(issue.asset, None, sender) + val senderBalanceBeforeSponsorFeeTx = senderBalanceBefore - issue.fee.value + val senderBalanceAfterSponsorFeeTx = senderBalanceBeforeSponsorFeeTx - sponsorFee.fee.value + val senderBalanceAfterSponsorFeeCancelTx = senderBalanceAfterSponsorFeeTx - sponsorFeeCancel.fee.value + + "BU-25. subscribe sponsorFee " in withGenerateSubscription( + settings = currentSettings, + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + ) { d => + d.appendBlock(issue) + d.appendBlock(sponsorFee) + } { updates => + val append = updates(2).append + val txUpdates = append.transactionStateUpdates.head + val assetDetails = txUpdates.assets.head + + checkSponsorFeeTransaction(append.transactionIds.head, append.transactionAt(0), sponsorFee) + checkBalances( + txUpdates.balances, + Map((senderAddress, Waves) -> (senderBalanceBeforeSponsorFeeTx, senderBalanceAfterSponsorFeeTx)) + ) + checkAssetsStateUpdates(assetDetails.before, issue, isNft = false, issue.quantity.value) + checkAssetsStateUpdates(assetDetails.after, issue, isNft = false, issue.quantity.value) + } + + "BU-27. subscribe sponsorFee cancel" in withGenerateSubscription( + settings = currentSettings, + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + ) { d => + d.appendBlock(issue) + d.appendBlock(sponsorFee) + d.appendMicroBlock(sponsorFeeCancel) + } { updates => + val append = updates(3).append + val txUpdates = append.transactionStateUpdates.head + val assetDetails = txUpdates.assets.head + + checkSponsorFeeTransaction(append.transactionIds.head, append.transactionAt(0), sponsorFeeCancel) + checkBalances( + txUpdates.balances, + Map((senderAddress, Waves) -> (senderBalanceAfterSponsorFeeTx, senderBalanceAfterSponsorFeeCancelTx)) + ) + checkAssetsStateUpdates(assetDetails.before, issue, isNft = false, issue.quantity.value) + checkAssetsStateUpdates(assetDetails.after, issue, isNft = false, issue.quantity.value) + } + } + + "BU-20. Return correct data for setAssetScript" in { + val complexScriptBefore = Option.apply(TxHelpers.script("true".stripMargin)) + val complexScriptAfter = TxHelpers.script("false".stripMargin) + val issue = TxHelpers.issue(sender, 99900000L, script = complexScriptBefore) + val setAssetScript = TxHelpers.setAssetScript(sender, issue.asset, complexScriptAfter, 1.waves) + val quantity = issue.quantity.value + val senderBalanceBeforeSetAssetScriptTx = senderBalanceBefore - issue.fee.value + val senderBalanceAfterSetAssetScriptTx = senderBalanceBeforeSetAssetScriptTx - setAssetScript.fee.value + + withGenerateSubscription( + settings = currentSettings.addFeatures(BlockchainFeatures.SmartAccounts), + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + ) { d => + d.appendBlock(issue) + d.appendMicroBlock(setAssetScript) + } { updates => + val append = updates(2).append + val txUpdates = append.transactionStateUpdates.head + val assetDetails = txUpdates.assets.head + checkSetAssetScriptTransaction(append.transactionIds.head, append.transactionAt(0), setAssetScript) + checkBalances( + txUpdates.balances, + Map( + (senderAddress, Waves) -> (senderBalanceBeforeSetAssetScriptTx, senderBalanceAfterSetAssetScriptTx) + ) + ) + checkAssetsStateUpdates(assetDetails.before, issue, isNft = false, quantity) + checkAssetsScriptStateUpdates(assetDetails.before.get.scriptInfo, complexScriptBefore.get.bytes.value().arr) + checkAssetsStateUpdates(assetDetails.after, issue, isNft = false, quantity) + checkAssetsScriptStateUpdates(assetDetails.after.get.scriptInfo, complexScriptAfter.bytes.value().arr) + } + } + + "BU-121. Return correct data for UpdateAssetInfo" in { + val newName = "new_name" + val newDescription = "new_description" + val issue = TxHelpers.issue(sender, 99900000L) + val updateAssetInfo = TxHelpers.updateAssetInfo(issue.assetId, newName, newDescription, sender) + val senderBalanceBeforeUpdateAssetInfoTx = senderBalanceBefore - issue.fee.value + val senderBalanceAfterUpdateAssetInfoTx = senderBalanceBeforeUpdateAssetInfoTx - updateAssetInfo.fee + + withGenerateSubscription( + settings = currentSettings.configure(_.copy(minAssetInfoUpdateInterval = 1)), + balances = Seq(AddrWithBalance(senderAddress, senderBalanceBefore)) + ) { d => + d.appendBlock(issue) + d.appendBlock() + d.appendMicroBlock(updateAssetInfo) + } { updates => + val append = updates(3).append + val txUpdates = append.transactionStateUpdates.head + val assetDetails = txUpdates.assets.head + checkUpdateAssetInfoTransaction(append.transactionIds.head, append.transactionAt(0), updateAssetInfo) + checkBalances( + txUpdates.balances, + Map( + (senderAddress, Waves) -> (senderBalanceBeforeUpdateAssetInfoTx, senderBalanceAfterUpdateAssetInfoTx) + ) + ) + checkAssetsStateUpdates(assetDetails.before, issue, isNft = false, issue.quantity.value) + checkAssetUpdatesStateUpdates(assetDetails.after, updateAssetInfo) + } + } + } +} diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/FakeObserver.scala b/grpc-server/src/test/scala/com/wavesplatform/events/FakeObserver.scala index 1dcd7016e7a..aa4aa578d82 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/FakeObserver.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/FakeObserver.scala @@ -70,12 +70,6 @@ object FakeObserver { ur.subscribe(request, obs) obs } - - def createSubscriptionObserver(request: SubscribeRequest): FakeObserver[SubscribeEvent] = { - val obs = FakeObserver.apply[SubscribeEvent] - ur.subscribe(request, obs) - obs - } } // Matchers diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/MetadataSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/MetadataSpec.scala index fdca72d53b2..28d043a2215 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/MetadataSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/MetadataSpec.scala @@ -1,7 +1,7 @@ package com.wavesplatform.events import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.events.FakeObserver.* +import FakeObserver.* import com.wavesplatform.events.api.grpc.protobuf.SubscribeRequest import com.wavesplatform.events.protobuf.TransactionMetadata import com.wavesplatform.protobuf.* diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/WithBUDomain.scala b/grpc-server/src/test/scala/com/wavesplatform/events/WithBUDomain.scala index 4ed07bc21d3..03a9832c591 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/WithBUDomain.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/WithBUDomain.scala @@ -2,7 +2,8 @@ package com.wavesplatform.events import com.google.common.util.concurrent.MoreExecutors import com.wavesplatform.db.WithDomain -import com.wavesplatform.events.FakeObserver.* +import FakeObserver.* +import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.events.api.grpc.protobuf.SubscribeRequest import com.wavesplatform.events.protobuf.BlockchainUpdated as PBBlockchainUpdated import com.wavesplatform.events.repo.LiquidState @@ -48,11 +49,13 @@ trait WithBUDomain extends WithDomain { _: Suite => } } - def withGenerateSubscription(request: SubscribeRequest = SubscribeRequest.of(1, Int.MaxValue), settings: WavesSettings)( - generateBlocks: Domain => Unit - )(f: Seq[PBBlockchainUpdated] => Unit): Unit = { + def withGenerateSubscription( + request: SubscribeRequest = SubscribeRequest.of(1, Int.MaxValue), + settings: WavesSettings, + balances: Seq[AddrWithBalance] = Seq(AddrWithBalance(TxHelpers.defaultSigner.toAddress, Constants.TotalWaves * Constants.UnitsInWave)) + )(generateBlocks: Domain => Unit)(f: Seq[PBBlockchainUpdated] => Unit): Unit = { withDomainAndRepo(settings) { (d, repo) => - d.appendBlock(TxHelpers.genesis(TxHelpers.defaultSigner.toAddress, Constants.TotalWaves * Constants.UnitsInWave)) + d.appendBlock(balances.map(awb => TxHelpers.genesis(awb.address, awb.balance))*) val subscription = repo.createFakeObserver(request) generateBlocks(d) diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/fixtures/PrepareInvokeTestData.scala b/grpc-server/src/test/scala/com/wavesplatform/events/fixtures/PrepareInvokeTestData.scala new file mode 100644 index 00000000000..352f1dfdb78 --- /dev/null +++ b/grpc-server/src/test/scala/com/wavesplatform/events/fixtures/PrepareInvokeTestData.scala @@ -0,0 +1,117 @@ +package com.wavesplatform.events.fixtures + +object PrepareInvokeTestData { + val scriptTransferIssueAssetNum: Long = 21000 + val scriptTransferUnitNum: Long = 22000 + val scriptTransferAssetNum: Long = 25000 + val paymentNum: Long = 30000 + val sponsorFeeAssetNum: Long = 35000 + val sponsorFeeIssueAssetNum: Long = 40000 + val reissueNum: Int = 50000 + val burnNum: Int = 100000 + val leaseNum: Long = 200000 + val baz = "baz" + val bar = "bar" + + val issueData: Map[String, Any] = Map( + "name" -> "issuedAssetName", + "description" -> "asset_ride_description", + "amount" -> 416168000, + "decimals" -> 8, + "nonce" -> 1 + ) + + val dataMap: Map[String, Any] = Map( + "intVal" -> 25400, + "stringVal" -> "test_string", + "booleanVal" -> "true" + ) + + def invokeAssetScript(libVersion: Int): String = + s""" + |{-# STDLIB_VERSION $libVersion #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + |@Callable(i) + |func setData(assetId:ByteVector, address:ByteVector)={ + |let issueAsset = Issue("${issueData.apply("name")}","${issueData.apply("description")}",${issueData.apply("amount")}, + |${issueData.apply("decimals")},true,unit,${issueData.apply("nonce")}) + |let issueAssetId = issueAsset.calculateAssetId() + |let lease = Lease(Address(address), $leaseNum) + | [ + | issueAsset, + | lease, + | LeaseCancel(lease.calculateLeaseId()), + | IntegerEntry("int", ${dataMap.apply("intVal")}), + | BinaryEntry("byte", assetId), + | BooleanEntry("bool", ${dataMap.apply("booleanVal")}), + | StringEntry("str", "${dataMap.apply("stringVal")}"), + | DeleteEntry("int"), + | Reissue(assetId, $reissueNum,true), + | Burn(assetId, $burnNum), + | ScriptTransfer(Address(address), $scriptTransferAssetNum, assetId), + | ScriptTransfer(Address(address), $scriptTransferIssueAssetNum, issueAssetId), + | ScriptTransfer(Address(address), $scriptTransferUnitNum, unit), + | SponsorFee(assetId, $sponsorFeeAssetNum), + | SponsorFee(issueAssetId, $sponsorFeeIssueAssetNum) + | ] + |} + |""".stripMargin + + def mainDAppScript(libVersion: Int): String = + s""" + |{-# STDLIB_VERSION $libVersion #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + |@Callable(i) + |func foo(acc1:ByteVector, acc2:ByteVector, a:Int, key1:String, assetId:ByteVector)={ + |strict res = invoke(Address(acc1),"$bar",[a, assetId, acc2],[AttachedPayment(assetId,$paymentNum)]) + |match res { + | case r : Int => + |( + | [ + | IntegerEntry(key1, r) + | ] + |) + | case _ => throw("Incorrect invoke result for res in dApp 1") + | } + |} + |""".stripMargin + + def nestedDAppScript(firstRecipient: String, libVersion: Int): String = + s""" + |{-# STDLIB_VERSION $libVersion #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + |@Callable(i) + |func $bar(a: Int, assetId: ByteVector, acc1: ByteVector)={ + |strict res2 = invoke(Address(acc1),"$baz",[a],[]) + |match res2 { + |case r: Int => + |( + | [ + | ScriptTransfer($firstRecipient, $scriptTransferAssetNum, assetId) + | ], + | a * 2 + |) + | case _ => throw("Incorrect invoke result for res2") + | } + |} + |""".stripMargin + + def doubleNestedDAppScript(secondRecipient: String, libVersion: Int): String = + s""" + |{-# STDLIB_VERSION $libVersion #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + |@Callable(i) + |func $baz(a: Int) = { + |( + | [ + | ScriptTransfer($secondRecipient, $scriptTransferUnitNum, unit) + | ], + |a + 2 + |) + |} + |""".stripMargin +} diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/fixtures/WavesTxChecks.scala b/grpc-server/src/test/scala/com/wavesplatform/events/fixtures/WavesTxChecks.scala new file mode 100644 index 00000000000..bf99ea808b9 --- /dev/null +++ b/grpc-server/src/test/scala/com/wavesplatform/events/fixtures/WavesTxChecks.scala @@ -0,0 +1,489 @@ +package com.wavesplatform.events.fixtures + +import com.google.protobuf.ByteString +import com.wavesplatform.account.Address +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.{Base58, EitherExt2} +import com.wavesplatform.events.StateUpdate.LeaseUpdate.LeaseStatus +import com.wavesplatform.events.protobuf.StateUpdate.AssetDetails.AssetScriptInfo +import com.wavesplatform.events.protobuf.StateUpdate.{AssetDetails, BalanceUpdate, DataEntryUpdate, LeaseUpdate, LeasingUpdate, ScriptUpdate} +import com.wavesplatform.events.protobuf.TransactionMetadata +import com.wavesplatform.protobuf.Amount +import com.wavesplatform.protobuf.order.Order +import com.wavesplatform.protobuf.transaction.* +import com.wavesplatform.protobuf.transaction.Transaction.Data +import com.wavesplatform.state.DataEntry +import com.wavesplatform.transaction.Asset.Waves +import com.wavesplatform.transaction.assets.* +import com.wavesplatform.transaction.assets.exchange.ExchangeTransaction +import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction} +import com.wavesplatform.transaction.smart.{InvokeScriptTransaction, SetScriptTransaction} +import com.wavesplatform.transaction.transfer.{MassTransferTransaction, TransferTransaction} +import com.wavesplatform.transaction.{Asset, CreateAliasTransaction, DataTransaction, EthereumTransaction, TransactionBase} +import org.scalactic.source.Position +import org.scalatest.OptionValues +import org.scalatest.matchers.should.Matchers + +object WavesTxChecks extends Matchers with OptionValues { + import PBAmounts.* + + def checkBaseTx(actualId: ByteString, actual: SignedTransaction, expected: TransactionBase)(implicit pos: Position): Unit = { + ByteStr(actualId.toByteArray) shouldEqual expected.id() + actual.transaction match { + case SignedTransaction.Transaction.WavesTransaction(value) => + val assetId = if (value.getFee.assetId.isEmpty) Waves else value.getFee.assetId + value.timestamp shouldEqual expected.timestamp + assetId shouldEqual expected.assetFee._1 + value.fee.value.amount shouldEqual expected.assetFee._2 + case _ => + } + } + + def checkCreateAlias(actualId: ByteString, actual: SignedTransaction, expected: CreateAliasTransaction)(implicit pos: Position): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.CreateAlias(value) => + value.alias shouldEqual expected.alias.name + case _ => fail("not a create alias transaction") + } + } + + def checkTransfer(actualId: ByteString, actual: SignedTransaction, expected: TransferTransaction, publicKeyHash: Array[Byte])(implicit + pos: Position + ): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.Transfer(value) => + value.amount.get.amount shouldEqual expected.amount.value + value.recipient.get.recipient.publicKeyHash.get.toByteArray shouldBe publicKeyHash + case _ => fail("not a Transfer transaction") + } + } + + def checkIssue(actualId: ByteString, actual: SignedTransaction, expected: IssueTransaction)(implicit + pos: Position + ): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.Issue(value) => + value.amount shouldEqual expected.quantity.value + value.decimals shouldBe expected.decimals.value + value.name shouldBe expected.name.toStringUtf8 + value.description shouldBe expected.description.toStringUtf8 + if (!value.script.isEmpty) value.script.toByteArray shouldBe expected.script.head.bytes.apply().arr + + case _ => fail("not a Issue transaction") + } + } + + def checkReissue(actualId: ByteString, actual: SignedTransaction, expected: ReissueTransaction)(implicit + pos: Position + ): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.Reissue(value) => + value.assetAmount.get.assetId.toByteArray shouldEqual expected.asset.id.arr + value.assetAmount.get.amount shouldEqual expected.quantity.value + value.reissuable shouldBe expected.reissuable + case _ => fail("not a Reissue transaction") + } + } + + def checkBurn(actualId: ByteString, actual: SignedTransaction, expected: BurnTransaction)(implicit + pos: Position + ): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.Burn(value) => + value.assetAmount.get.assetId.toByteArray shouldEqual expected.asset.id.arr + value.assetAmount.get.amount shouldEqual expected.quantity.value + case _ => fail("not a Burn transaction") + } + } + + def checkExchange(actualId: ByteString, actual: SignedTransaction, expected: ExchangeTransaction)(implicit pos: Position): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.Exchange(value) => + value.amount shouldEqual expected.amount.value + value.price shouldEqual expected.price.value + value.buyMatcherFee shouldEqual expected.buyMatcherFee + value.sellMatcherFee shouldEqual expected.sellMatcherFee + checkOrders(value.orders.head, expected.order1) + checkOrders(value.orders.last, expected.order2) + case _ => fail("not a Exchange transaction") + } + } + + def checkLease(actualId: ByteString, actual: SignedTransaction, expected: LeaseTransaction, publicKeyHash: Array[Byte])(implicit + pos: Position + ): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.Lease(value) => + value.recipient.get.recipient.publicKeyHash.get.toByteArray shouldBe publicKeyHash + value.amount shouldBe expected.amount.value + case _ => fail("not a Lease transaction") + } + } + + def checkLeaseCancel(actualId: ByteString, actual: SignedTransaction, expected: LeaseCancelTransaction)(implicit + pos: Position + ): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.LeaseCancel(value) => + value.leaseId.toByteArray shouldBe expected.leaseId.arr + case _ => fail("not a LeaseCancel transaction") + } + } + + def checkMassTransfer(actualId: ByteString, actual: SignedTransaction, expected: MassTransferTransaction, pkHashes: Seq[Array[Byte]])(implicit + pos: Position + ): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.MassTransfer(value) => + value.assetId.toByteArray shouldBe expected.assetId.compatId.get.arr + value.transfers.foreach(actualTransfer => + expected.transfers.foreach(expectedTransfer => actualTransfer.amount shouldBe expectedTransfer.amount.value) + ) + value.transfers.zip(pkHashes).foreach(item => item._1.getRecipient.getPublicKeyHash.toByteArray shouldBe item._2) + case _ => fail("not a MassTransfer transaction") + } + } + + def checkDataTransaction(actualId: ByteString, actual: SignedTransaction, expected: DataTransaction)(implicit pos: Position): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.DataTransaction(value) => + val actualData = value.data.map(entry => (entry.key, entry.value.value)).toMap + val expectedData = expected.data.map(entry => (entry.key, entry.value)).toMap + + actualData.keySet shouldBe expectedData.keySet + + for ((key, actualValue) <- actualData) { + val expectedValue = expectedData(key) + + if (key == "Binary") { + actualValue shouldBe ByteString.copyFrom(Base58.decode(expectedValue.toString)) + } else { + actualValue shouldBe expectedValue + } + } + case _ => fail("not a Data transaction") + } + } + + def checkSetScriptTransaction(actualId: ByteString, actual: SignedTransaction, expected: SetScriptTransaction)(implicit pos: Position): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.SetScript(value) => + value.script.toByteArray shouldBe expected.script.get.bytes.value().arr + case _ => fail("not a SetScript transaction") + } + } + + def checkSetAssetScriptTransaction(actualId: ByteString, actual: SignedTransaction, expected: SetAssetScriptTransaction)(implicit + pos: Position + ): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.SetAssetScript(value) => + value.assetId.toByteArray shouldBe expected.asset.id.arr + value.script.toByteArray shouldBe expected.script.get.bytes.value().arr + case _ => fail("not a SetAssetScript transaction") + } + } + + def checkUpdateAssetInfoTransaction(actualId: ByteString, actual: SignedTransaction, expected: UpdateAssetInfoTransaction)(implicit + pos: Position + ): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.UpdateAssetInfo(value) => + value.assetId.toByteArray shouldBe expected.assetId.id.arr + value.name shouldBe expected.name + case _ => fail("not a UpdateAssetInfo transaction") + } + } + + def checkSponsorFeeTransaction(actualId: ByteString, actual: SignedTransaction, expected: SponsorFeeTransaction)(implicit pos: Position): Unit = { + checkBaseTx(actualId, actual, expected) + val expectedAmount = expected.minSponsoredAssetFee.map(_.toString.toLong).getOrElse(0L) + actual.transaction.wavesTransaction.value.data match { + case Data.SponsorFee(value) => + value.minFee.value.assetId.toByteArray shouldBe expected.asset.id.arr + value.minFee.value.amount shouldBe expectedAmount + case _ => fail("not a SponsorFee transaction") + } + } + + def checkInvokeTransaction(actualId: ByteString, actual: SignedTransaction, expected: InvokeScriptTransaction, publicKeyHash: Array[Byte])(implicit + pos: Position + ): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.wavesTransaction.value.data match { + case Data.InvokeScript(value) => + value.dApp.get.getPublicKeyHash.toByteArray shouldBe publicKeyHash + case _ => fail("not a InvokeScript transaction") + } + } + + def checkInvokeBaseTransactionMetadata(actual: Seq[TransactionMetadata], expected: InvokeScriptTransaction)(implicit + pos: Position + ): Unit = { + val invokeScript = actual.head.getInvokeScript + + actual.head.senderAddress.toByteArray shouldBe expected.senderAddress.bytes + invokeScript.dAppAddress.toByteArray shouldBe expected.dApp.bytes + invokeScript.functionName shouldBe expected.funcCallOpt.get.function.funcName + } + + def checkArguments(expectedValues: List[Any], actualArguments: List[Any]): Unit = { + if (expectedValues.size != actualArguments.size) { + throw new IllegalArgumentException("Number of expected values does not match the number of actual arguments.") + } + expectedValues.zip(actualArguments).foreach(item => item._1 shouldBe item._2) + } + + def checkInvokeScriptResultData(result: Seq[DataTransactionData.DataEntry], actualData: Seq[(String, Any)])(implicit pos: Position): Unit = { + if (result.length != actualData.length) { + throw new IllegalArgumentException("Number of expected values does not match the number of actual data arguments.") + } + + result.zip(actualData).foreach { case (entry, (key, expectedValue)) => + if (entry.key != key) { + throw new IllegalArgumentException(s"Key mismatch: expected '$key', but got '${entry.key}'.") + } + + val actualValue = if (entry.value.isDefined) entry.value.value match { + case byteStr: ByteString => Some(byteStr.toByteArray) + case otherValue => Some(otherValue) + } + else None + + actualValue.getOrElse(None) shouldBe expectedValue + } + } + + def checkInvokeScriptResultIssues(actualIssue: InvokeScriptResult.Issue, expectedIssues: Map[String, Any])(implicit + pos: Position + ): Unit = { + expectedIssues.foreach { case (fieldName, expectedValue) => + val actualValue = fieldName match { + case "name" => actualIssue.name + case "description" => actualIssue.description + case "amount" => actualIssue.amount + case "decimals" => actualIssue.decimals + case "nonce" => actualIssue.nonce + case _ => throw new IllegalArgumentException(s"Unknown issue field: $fieldName") + } + actualValue shouldBe expectedValue + } + } + + def checkInvokeScriptBaseInvokes(invoke: InvokeScriptResult.Invocation, address: Address, funcName: String): Unit = { + val actualAddress = Address.fromBytes(invoke.dApp.toByteArray).explicitGet() + actualAddress shouldBe address + invoke.call.get.function shouldBe funcName + } + + def checkInvokeScriptInvokesArgs(actualArgument: InvokeScriptResult.Call.Argument, expectedValue: Any): Unit = { + val actualEntry = actualArgument.value + + if (actualEntry.isDefined && actualEntry.isBinaryValue) { + actualEntry.binaryValue.get.toByteArray shouldBe expectedValue + } else if (actualEntry.isDefined) { + actualEntry.value shouldBe expectedValue + } + } + + def checkInvokeScriptInvokesPayments(actualPayment: Amount, asset: Asset, amount: Long): Unit = { + val actualAssetId = toVanillaAssetId(actualPayment.assetId) + actualAssetId shouldBe asset + actualPayment.amount shouldBe amount + } + + def checkInvokeScriptResultTransfers(transfer: InvokeScriptResult.Payment, address: Address, amount: Long, assetId: Asset)(implicit + pos: Position + ): Unit = { + val actualAddress = Address.fromBytes(transfer.address.toByteArray).explicitGet() + val actualAssetId = toVanillaAssetId(transfer.amount.value.assetId) + + actualAddress shouldBe address + transfer.amount.value.amount shouldBe amount + actualAssetId shouldBe assetId + } + + def checkInvokeScriptResultReissue(reissue: InvokeScriptResult.Reissue, assetId: Asset, amount: Long, reissuable: Boolean)(implicit + pos: Position + ): Unit = { + val actualAssetId = toVanillaAssetId(reissue.assetId) + actualAssetId shouldBe assetId + reissue.amount shouldBe amount + reissue.isReissuable shouldBe reissuable + } + + def checkInvokeScriptResultBurn(burn: InvokeScriptResult.Burn, assetId: Asset, amount: Long)(implicit + pos: Position + ): Unit = { + val actualAssetId = toVanillaAssetId(burn.assetId) + + actualAssetId shouldBe assetId + burn.amount shouldBe amount + } + + def checkInvokeScriptResultSponsorFee(sponsorFee: InvokeScriptResult.SponsorFee, assetId: Asset, amount: Long)(implicit + pos: Position + ): Unit = { + val actualAssetId = toVanillaAssetId(sponsorFee.minFee.value.assetId) + actualAssetId shouldBe assetId + sponsorFee.minFee.value.amount shouldBe amount + } + + def checkInvokeScriptResultLease(leases: InvokeScriptResult.Lease, publicKeyHash: Array[Byte], amount: Long): Unit = { + leases.recipient.value.getPublicKeyHash.toByteArray shouldBe publicKeyHash + leases.amount shouldBe amount + } + + def checkInvokeScriptResultLeaseCancel(leasesCancel: InvokeScriptResult.LeaseCancel, leaseId: Array[Byte]): Unit = { + leasesCancel.leaseId.toByteArray shouldBe leaseId + } + + def checkEthereumTransaction(actualId: ByteString, actual: SignedTransaction, expected: EthereumTransaction)(implicit pos: Position): Unit = { + checkBaseTx(actualId, actual, expected) + actual.transaction.ethereumTransaction.value.toByteArray shouldBe expected.bytes.value() + } + + def checkBalances(actual: Seq[BalanceUpdate], expected: Map[(Address, Asset), (Long, Long)])(implicit pos: Position): Unit = { + actual.map { bu => + ( + (Address.fromBytes(bu.address.toByteArray).explicitGet(), toVanillaAssetId(bu.amountAfter.value.assetId)), + (bu.amountBefore, bu.amountAfter.value.amount) + ) + }.toMap shouldBe expected + } + + def checkMassTransferBalances(actual: Seq[BalanceUpdate], expected: Map[(Address, Asset), (Long, Long)]): Unit = { + val actualBalances = actual.map { bu => + ( + (Address.fromBytes(bu.address.toByteArray).explicitGet(), toVanillaAssetId(bu.amountAfter.value.assetId)), + (bu.amountBefore, bu.amountAfter.value.amount) + ) + }.toMap + val matchingKeys = actualBalances.keySet.intersect(expected.keySet) + matchingKeys.foreach { key => + actualBalances(key) shouldBe expected(key) + } + } + + def checkDataEntriesStateUpdate(actual: Seq[DataEntryUpdate], expectedData: Seq[DataEntry[?]], expectAddress: Array[Byte]): Unit = { + actual.zip(expectedData).foreach { case (actualUpdate, expectedEntry) => + val actualEntry = actualUpdate.dataEntry.get + val actualKey = actualEntry.key + val actualValue = if (actualEntry.value.isDefined) actualEntry.value.value else actualEntry.value + val expectedKey = expectedEntry.key + val expectedValue = expectedEntry.value + + actualUpdate.address.toByteArray shouldBe expectAddress + actualKey shouldBe expectedKey + + if (actualEntry.value.isDefined && actualEntry.value.isBinaryValue) { + actualValue shouldBe ByteString.copyFrom(Base58.decode(expectedValue.toString)) + } else if (actualEntry.value.isDefined) { + actualValue shouldBe expectedValue + } + } + } + + def checkAssetsStateUpdates(actual: Option[AssetDetails], expected: IssueTransaction, isNft: Boolean, quantityValue: Long): Unit = { + actual.get.assetId.toByteArray shouldBe expected.asset.id.arr + actual.get.issuer.toByteArray shouldBe expected.sender.arr + actual.get.name shouldBe expected.name.toStringUtf8 + actual.get.description shouldBe expected.description.toStringUtf8 + actual.get.nft shouldBe isNft + actual.get.volume shouldBe quantityValue + actual.get.decimals shouldBe expected.decimals.value + actual.get.reissuable shouldBe expected.reissuable + } + + def checkAssetsStateUpdates(actual: Option[AssetDetails], expected: BurnTransaction, isNft: Boolean, quantityAfterReissue: Long): Unit = { + actual.get.assetId.toByteArray shouldBe expected.asset.id.arr + actual.get.issuer.toByteArray shouldBe expected.sender.arr + actual.get.nft shouldBe isNft + actual.get.volume shouldBe quantityAfterReissue + } + + def checkAssetsStateUpdates(actual: Option[AssetDetails], expected: ReissueTransaction, isNft: Boolean, quantityAfterReissue: Long): Unit = { + actual.get.assetId.toByteArray shouldBe expected.asset.id.arr + actual.get.issuer.toByteArray shouldBe expected.sender.arr + actual.get.nft shouldBe isNft + actual.get.volume shouldBe quantityAfterReissue + actual.get.reissuable shouldBe expected.reissuable + } + + def checkAssetsStateUpdates(actual: Option[AssetDetails], expected: Map[String, Any], assetId: Asset, issuer: Array[Byte]): Unit = { + toVanillaAssetId(actual.get.assetId) shouldBe assetId + actual.get.issuer.toByteArray shouldBe issuer + actual.get.name shouldBe expected.apply("name") + actual.get.description shouldBe expected.apply("description") + actual.get.volume shouldBe expected.apply("amount") + actual.get.decimals shouldBe expected.apply("decimals") + } + + def checkAssetUpdatesStateUpdates(actual: Option[AssetDetails], expected: UpdateAssetInfoTransaction): Unit = { + actual.get.name shouldBe expected.name + actual.get.description shouldBe expected.description + } + + def checkAssetsScriptStateUpdates(actual: Option[AssetScriptInfo], expected: Array[Byte]): Unit = { + actual.get.script.toByteArray shouldBe expected + } + + def checkLeasingForAddress(actual: Seq[LeasingUpdate], expected: Map[(Address, Long, Long), (Long, Long)]): Unit = { + actual.map { bu => + ( + (Address.fromBytes(bu.address.toByteArray).explicitGet(), bu.inAfter, bu.outAfter), + (bu.inBefore, bu.outBefore) + ) + }.toMap shouldBe expected + } + + def checkIndividualLeases( + actual: Seq[LeaseUpdate], + expected: Map[(LeaseStatus, Long), (Array[Byte], Array[Byte], Array[Byte], Array[Byte])] + ): Unit = { + actual.size shouldBe expected.size + + actual.zip(expected).foreach { case (lease, ((status, amount), (leaseId, sender, recipient, originTransactionId))) => + lease.statusAfter.toString() shouldBe status.toString.toUpperCase + lease.amount shouldBe amount + lease.leaseId.toByteArray shouldBe leaseId + lease.sender.toByteArray shouldBe sender + lease.recipient.toByteArray shouldBe recipient + lease.originTransactionId.toByteArray shouldBe originTransactionId + } + } + + def checkSetScriptStateUpdate(actual: ScriptUpdate, expected: SetScriptTransaction)(implicit pos: Position): Unit = { + actual.address.toByteArray shouldBe expected.sender.toAddress.bytes + actual.after.toByteArray shouldBe expected.script.get.bytes.value().arr + actual.before.toByteArray.isEmpty shouldBe true + } + + private def checkOrders(order: Order, expected: exchange.Order): Unit = { + order.sender.senderPublicKey.get.toByteArray shouldBe expected.sender.arr + order.matcherPublicKey.toByteArray shouldBe expected.matcherPublicKey.arr + order.assetPair.get.amountAssetId.toByteArray shouldBe expected.assetPair.amountAsset.compatId.get.arr + order.assetPair.get.priceAssetId.toByteArray shouldBe expected.assetPair.priceAsset.compatId.get.arr + order.orderSide.toString() equalsIgnoreCase expected.orderType.toString + order.amount shouldBe expected.amount.value + order.price shouldBe expected.price.value + order.timestamp shouldBe expected.timestamp + order.expiration shouldBe expected.expiration + order.matcherFee.get.amount shouldBe expected.matcherFee.value + toVanillaAssetId(order.matcherFee.get.assetId) shouldBe expected.matcherFeeAssetId + order.version shouldBe expected.version + } +} diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/package.scala b/grpc-server/src/test/scala/com/wavesplatform/events/package.scala new file mode 100644 index 00000000000..a553213091f --- /dev/null +++ b/grpc-server/src/test/scala/com/wavesplatform/events/package.scala @@ -0,0 +1,28 @@ +package com.wavesplatform + +import com.wavesplatform.events.protobuf.BlockchainUpdated as PBBlockchainUpdated +import com.wavesplatform.events.protobuf.BlockchainUpdated.Append.Body +import com.wavesplatform.events.protobuf.BlockchainUpdated.{Append, Update} +import com.wavesplatform.protobuf.transaction.SignedTransaction +import org.scalactic.source.Position +import org.scalatest.OptionValues.* +import org.scalatest.matchers.should.Matchers + +package object events { + implicit class BlockchainUpdatedExt(val se: PBBlockchainUpdated) extends Matchers { + def append(implicit pos: Position): Append = + se.update match { + case Update.Append(a) => a + case other => fail(s"${other.getClass.getSimpleName} is not an Append") + } + } + + implicit class AppendExt(val append: Append) extends Matchers { + def transactionAt(idx: Int)(implicit pos: Position): SignedTransaction = + append.body match { + case Body.Empty => fail("empty update body") + case Body.Block(value) => value.block.value.transactions.lift(idx).value + case Body.MicroBlock(value) => value.microBlock.value.microBlock.value.transactions.lift(idx).value + } + } +} diff --git a/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala b/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala index 656973caccd..042bbf7e3a2 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala @@ -42,6 +42,16 @@ object TxHelpers { def defaultEthSigner: ECKeyPair = defaultSigner.toEthKeyPair + def accountSeqGenerator(numberAccounts: Int, amount: Long): Seq[ParsedTransfer] = { + val firstAccountNum = 100 + val lastAccountNum = firstAccountNum + numberAccounts + val accountsSeq = (firstAccountNum until lastAccountNum).map { num => + val recipient = signer(num).toAddress + ParsedTransfer(recipient, TxNonNegativeAmount.unsafeFrom(amount)) + } + accountsSeq + } + val matcher: SeedKeyPair = defaultSigner private[this] var lastTimestamp = System.currentTimeMillis()