From aa19d4ce9e9244565f8e566317cb7dd4b857586c Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Thu, 12 May 2022 12:28:06 +0300 Subject: [PATCH] NODE-2450 Fix GrpcSponsorFeeActionSuite (#3689) --- .../GrpcIssueReissueBurnAssetSuite.scala | 51 +- .../GrpcScriptAssetActionLimitsSuite.scala | 155 ++++++ .../it/asset/GrpcSponsorFeeActionSuite.scala | 454 ++++++++---------- .../HttpScriptAssetActionLimitsSuite.scala | 141 ++++++ .../asset/ScriptAssetActionLimitsSuite.scala | 154 +----- 5 files changed, 521 insertions(+), 434 deletions(-) create mode 100644 node-it/src/test/scala/com/wavesplatform/it/asset/GrpcScriptAssetActionLimitsSuite.scala create mode 100644 node-it/src/test/scala/com/wavesplatform/it/asset/HttpScriptAssetActionLimitsSuite.scala diff --git a/node-it/src/test/scala/com/wavesplatform/it/asset/GrpcIssueReissueBurnAssetSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/asset/GrpcIssueReissueBurnAssetSuite.scala index fc16ce9be5d..077191e7544 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/asset/GrpcIssueReissueBurnAssetSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/asset/GrpcIssueReissueBurnAssetSuite.scala @@ -4,15 +4,15 @@ import com.wavesplatform.account.KeyPair import com.wavesplatform.api.grpc.AssetInfoResponse import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, EitherExt2} -import com.wavesplatform.it.api.SyncGrpcApi._ +import com.wavesplatform.it.api.SyncGrpcApi.* import com.wavesplatform.it.api.{BurnInfoResponse, IssueInfoResponse, ReissueInfoResponse, StateChangesDetails} -import com.wavesplatform.it.sync._ +import com.wavesplatform.it.sync.* import com.wavesplatform.it.sync.grpc.GrpcBaseTransactionSuiteLike import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BOOLEAN, CONST_BYTESTR, CONST_LONG, FUNCTION_CALL} import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 import com.wavesplatform.protobuf.transaction.{PBRecipients, PBTransactions} -import com.wavesplatform.test._ +import com.wavesplatform.test.* import com.wavesplatform.transaction.TxVersion import com.wavesplatform.transaction.smart.SetScriptTransaction import com.wavesplatform.transaction.smart.script.ScriptCompiler @@ -74,8 +74,7 @@ class GrpcIssueReissueBurnAssetSuite extends AnyFreeSpec with GrpcBaseTransactio sender.assertAssetBalance(acc, assetId, 0) if (isCallable) assertStateChanges(tx) { sd => - sd.burns should matchPattern { - case Seq(BurnInfoResponse(`assetId`, data.quantity)) => + sd.burns should matchPattern { case Seq(BurnInfoResponse(`assetId`, data.quantity)) => } } @@ -170,34 +169,6 @@ class GrpcIssueReissueBurnAssetSuite extends AnyFreeSpec with GrpcBaseTransactio sender.assetInfo(assetId).totalVolume should be(simpleReissuableAsset.quantity + 1000) } - - "Issue 10 assets should not produce an error" in { - val acc = createDapp(script(simpleNonreissuableAsset)) - val tx = invokeScript(acc, "issue10Assets", fee = invocationCost(issuesCount = 10)) - for (nth <- 0 to 9) { - val assetId = validateIssuedAssets(acc, tx, simpleNonreissuableAsset, nth, CallableMethod) - assertQuantity(assetId)(simpleNonreissuableAsset.quantity, reissuable = false) - sender.assertAssetBalance(acc, assetId, simpleNonreissuableAsset.quantity) - } - } - - "Issue more than 10 assets should produce an error" in { - val acc = createDapp(script(simpleNonreissuableAsset)) - assertGrpcError(invokeScript(acc, "issue11Assets"), "Actions count limit is exceeded") - } - - "More than 10 actions Issue/Reissue/Burn should produce an error" in { - val acc = createDapp(script(simpleReissuableAsset)) - val txIssue = issue(acc, method, simpleReissuableAsset, invocationCost(1)) - val assetId = validateIssuedAssets(acc, txIssue, simpleReissuableAsset, method = method) - - assertGrpcError(invokeScript(acc, "process11actions", assetId = assetId), "Actions count limit is exceeded") - } - - "More than 10 issue action in one invocation should produce an error" in { - val acc = createDapp(script(simpleNonreissuableAsset)) - assertGrpcError(invokeScript(acc, "issue11Assets", fee = invocationCost(1)), "Actions count limit is exceeded") - } } "State changes" - { @@ -313,9 +284,9 @@ class GrpcIssueReissueBurnAssetSuite extends AnyFreeSpec with GrpcBaseTransactio case "reissueIssueAndNft" => List(CONST_BYTESTR(ByteStr.decodeBase58(assetId).get).explicitGet()) case "process11actions" => List(CONST_BYTESTR(ByteStr.decodeBase58(assetId).get).explicitGet()) case "burnAsset" => List(CONST_BYTESTR(ByteStr.decodeBase58(assetId).get).explicitGet(), CONST_LONG(count)) - case "reissueAsset" => List(CONST_BYTESTR(ByteStr.decodeBase58(assetId).get).explicitGet(), CONST_BOOLEAN(isReissuable), CONST_LONG(count)) - case "reissueAndReissue" => List(CONST_BYTESTR(ByteStr.decodeBase58(assetId).get).explicitGet(), CONST_LONG(count)) - case _ => Nil + case "reissueAsset" => List(CONST_BYTESTR(ByteStr.decodeBase58(assetId).get).explicitGet(), CONST_BOOLEAN(isReissuable), CONST_LONG(count)) + case "reissueAndReissue" => List(CONST_BYTESTR(ByteStr.decodeBase58(assetId).get).explicitGet(), CONST_LONG(count)) + case _ => Nil } val fc = FUNCTION_CALL(FunctionHeader.User(function), args) @@ -360,7 +331,7 @@ class GrpcIssueReissueBurnAssetSuite extends AnyFreeSpec with GrpcBaseTransactio } def assertGrpcAssetDetails(assetId: String)(f: AssetInfoResponse => Unit): Unit = { - import com.wavesplatform.it.api.SyncGrpcApi._ + import com.wavesplatform.it.api.SyncGrpcApi.* val assetInfo = sender.grpc.assetInfo(assetId) f(assetInfo) } @@ -442,8 +413,7 @@ class GrpcIssueReissueBurnAssetSuite extends AnyFreeSpec with GrpcBaseTransactio val tx = invokeScript(account, "reissueAsset", assetId = assetId, count = quantity, isReissuable = reissuable) if (checkStateChanges) assertStateChanges(tx) { sd => - sd.reissues should matchPattern { - case Seq(ReissueInfoResponse(`assetId`, `reissuable`, `quantity`)) => + sd.reissues should matchPattern { case Seq(ReissueInfoResponse(`assetId`, `reissuable`, `quantity`)) => } } tx @@ -457,8 +427,7 @@ class GrpcIssueReissueBurnAssetSuite extends AnyFreeSpec with GrpcBaseTransactio case CallableMethod => val tx = invokeScript(account, "burnAsset", assetId = assetId, count = quantity, fee = fee) assertStateChanges(tx) { sd => - sd.burns should matchPattern { - case Seq(BurnInfoResponse(`assetId`, `quantity`)) => + sd.burns should matchPattern { case Seq(BurnInfoResponse(`assetId`, `quantity`)) => } } tx diff --git a/node-it/src/test/scala/com/wavesplatform/it/asset/GrpcScriptAssetActionLimitsSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/asset/GrpcScriptAssetActionLimitsSuite.scala new file mode 100644 index 00000000000..2f38502f704 --- /dev/null +++ b/node-it/src/test/scala/com/wavesplatform/it/asset/GrpcScriptAssetActionLimitsSuite.scala @@ -0,0 +1,155 @@ +package com.wavesplatform.it.asset + +import com.google.protobuf.ByteString +import com.wavesplatform.account.KeyPair +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.it.api.SyncGrpcApi.* +import com.wavesplatform.it.sync.grpc.GrpcBaseTransactionSuiteLike +import com.wavesplatform.it.sync.{issueFee, minFee, smartMinFee} +import com.wavesplatform.lang.directives.values.{V4, V5, V6} +import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader} +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BYTESTR, FUNCTION_CALL} +import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 +import com.wavesplatform.protobuf.transaction.PBRecipients +import com.wavesplatform.test.* +import com.wavesplatform.transaction.smart.script.ScriptCompiler + +class GrpcScriptAssetActionLimitsSuite extends ScriptAssetActionLimitsSuite with GrpcBaseTransactionSuiteLike { + + def createDApp(script: String, address: KeyPair = miner.generateKeyPair()): KeyPair = { + val compiledScript = ScriptCompiler + .compile( + script, + ScriptEstimatorV2 + ) + .explicitGet() + ._1 + + miner.broadcastTransfer(sender.keyPair, PBRecipients.create(address.toAddress), initialWavesBalance, minFee, waitForTx = true) + + nodes.foreach( + _.waitForTxAndHeightArise( + miner.setScript(address, Right(Some(compiledScript)), fee = 1.waves, timestamp = System.currentTimeMillis(), waitForTx = true).id + ) + ) + + address + } + + Seq(V4, V5, V6).foreach { version => + val actionsLimit = + if (version == V6) + ContractLimits.MaxAssetScriptActionsAmountV6 + else + ContractLimits.MaxCallableActionsAmountBeforeV6(version) + + val errMsg = + if (version == V6) + "Issue, Reissue, Burn, SponsorFee actions count limit is exceeded" + else + "Actions count limit is exceeded" + + s"SponsorFee actions count limit for V${version.id}" in { + val dApp = createSponsorFeeDApp(actionsLimit, version) + val dAppAddress = byteStringAddress(dApp) + val invokeTx1 = miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User(s"issue${actionsLimit}assets"), List.empty)), + waitForTx = true, + fee = smartMinFee + issueFee * actionsLimit + ) + val invokeTx2 = miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User(s"sponsor${actionsLimit}assets"), List.empty)), + waitForTx = true, + fee = smartMinFee + ) + + val assetIds = miner.stateChanges(invokeTx1.id)._2.issues.map(_.assetId) + val sponsorFees = miner.stateChanges(invokeTx2.id)._2.sponsorFees + + (assetIds zip sponsorFees) + .foreach { case (issueAssetId, sponsorFee) => + issueAssetId shouldBe sponsorFee.assetId + sponsorFee.minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) + + val assetInfo = miner.assetInfo(issueAssetId) + assetInfo.sponsorship shouldBe minSponsoredAssetFee + assetInfo.sponsorBalance shouldBe miner.wavesBalance(dAppAddress).regular + + miner.assetsBalance(dAppAddress).contains(issueAssetId) shouldBe true + } + + assertGrpcError( + miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User(s"sponsor${actionsLimit + 1}assets"), List.empty)), + fee = smartMinFee + ), + errMsg + ) + } + + s"Issue $actionsLimit assets should not produce an error for V${version.id}" in { + val acc = createDApp(script(actionsLimit, version)) + val tx = miner.broadcastInvokeScript( + acc, + PBRecipients.create(acc.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User(s"issue${actionsLimit}Assets"), List.empty)), + fee = smartMinFee + issueFee * actionsLimit, + waitForTx = true + ) + for (nth <- 0 until actionsLimit) { + val assetInfo = sender.stateChanges(tx.id)._2.issues(nth) + assetInfo.quantity shouldBe asset.quantity + assetInfo.name shouldBe asset.name + assetInfo.description shouldBe asset.description + assetInfo.decimals shouldBe asset.decimals + assetInfo.isReissuable shouldBe asset.reissuable + sender.assertAssetBalance(acc.toAddress.toString, assetInfo.assetId, asset.quantity) + } + } + + s"More than $actionsLimit Issue/Reissue/Burn/SponsorFee actions should produce an error for V${version.id}" in { + val acc = createDApp(script(actionsLimit, version)) + val assetId = miner.broadcastIssue(acc, "test", 100, 8, reissuable = true, fee = issueFee, waitForTx = true).id + + assertGrpcError( + miner.broadcastInvokeScript( + acc, + PBRecipients.create(acc.toAddress), + Some( + FUNCTION_CALL( + FunctionHeader.User(s"process${actionsLimit + 1}actions"), + List(CONST_BYTESTR(ByteStr.decodeBase58(assetId).get).explicitGet()) + ) + ), + fee = smartMinFee + issueFee * (actionsLimit + 1), + waitForTx = true + ), + errMsg + ) + } + + s"More than $actionsLimit issue actions should produce an error for V${version.id}" in { + val acc = createDApp(script(actionsLimit, version)) + assertGrpcError( + miner.broadcastInvokeScript( + acc, + PBRecipients.create(acc.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User(s"issue${actionsLimit + 1}Assets"), List.empty)), + fee = smartMinFee + issueFee * (actionsLimit + 1), + waitForTx = true + ), + errMsg + ) + } + } + + private def byteStringAddress(account: KeyPair): ByteString = + PBRecipients.create(account.toAddress).getPublicKeyHash +} diff --git a/node-it/src/test/scala/com/wavesplatform/it/asset/GrpcSponsorFeeActionSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/asset/GrpcSponsorFeeActionSuite.scala index 6c34dfb71c9..4ecd1bb960d 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/asset/GrpcSponsorFeeActionSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/asset/GrpcSponsorFeeActionSuite.scala @@ -1,25 +1,28 @@ package com.wavesplatform.it.asset +import com.google.protobuf.ByteString import com.wavesplatform.account.KeyPair import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.it.api.SyncHttpApi._ +import com.wavesplatform.it.api.SyncGrpcApi.* import com.wavesplatform.it.api.{IssueInfoResponse, SponsorFeeResponse, StateChangesDetails} -import com.wavesplatform.it.sync._ +import com.wavesplatform.it.sync.* import com.wavesplatform.it.sync.grpc.GrpcBaseTransactionSuiteLike +import com.wavesplatform.lang.v1.FunctionHeader +import com.wavesplatform.lang.v1.compiler.Terms.FUNCTION_CALL import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 +import com.wavesplatform.protobuf.transaction.PBRecipients import com.wavesplatform.state.Sponsorship -import com.wavesplatform.test._ -import com.wavesplatform.transaction.smart.SetScriptTransaction +import com.wavesplatform.test.* import com.wavesplatform.transaction.smart.script.ScriptCompiler import org.scalatest.freespec.AnyFreeSpec class GrpcSponsorFeeActionSuite extends AnyFreeSpec with GrpcBaseTransactionSuiteLike { private val initialWavesBalance = 100.waves - private val minSponsoredAssetFee = 100 - private var sponsoredAssetId: String = "" - private var globalDAppAddress: String = "" - private var dApp: KeyPair = _ + private val minSponsoredAssetFee = 100 + private var sponsoredAssetId: String = "" + private var globalDAppAddress: ByteString = "" + private var dApp: KeyPair = _ protected override def beforeAll(): Unit = { super.beforeAll() @@ -39,83 +42,85 @@ class GrpcSponsorFeeActionSuite extends AnyFreeSpec with GrpcBaseTransactionSuit """.stripMargin ) - globalDAppAddress = dApp.toAddress.toString + globalDAppAddress = byteStringAddress(dApp) } "State changes" - { "Issue and sponsor from dApp" in { - val invokeTx = miner.invokeScript(miner.keyPair, globalDAppAddress, Some("issueAndSponsor"), waitForTx = true, fee = smartMinFee + issueFee) + val invokeTx = miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User("issueAndSponsor"), List.empty)), + fee = smartMinFee + issueFee, + waitForTx = true + ) val matchDebugResult = matchPattern { - case Seq( - StateChangesDetails( - Nil, - Nil, - Seq(IssueInfoResponse(issueAssetId, _, _, _, _, _, _, _)), - Nil, - Nil, - Seq(SponsorFeeResponse(sponsorFeeAssetId, Some(`minSponsoredAssetFee`))), - None, - Nil - ) + case StateChangesDetails( + Nil, + Nil, + Seq(IssueInfoResponse(issueAssetId, _, _, _, _, _, _, _)), + Nil, + Nil, + Seq(SponsorFeeResponse(sponsorFeeAssetId, Some(`minSponsoredAssetFee`))), + None, + Nil ) if issueAssetId == sponsorFeeAssetId => } - val stateChanges = miner.debugStateChanges(invokeTx._1.id).stateChanges.toSeq + val (_, stateChanges) = miner.stateChanges(invokeTx.id) stateChanges should matchDebugResult - miner.debugStateChangesByAddress(globalDAppAddress, limit = 100).flatMap(_.stateChanges) should matchDebugResult + val (_, addressStateChanges) = miner.stateChanges(minerAddress).head + addressStateChanges should matchDebugResult - sponsoredAssetId = stateChanges.head.sponsorFees.head.assetId - miner.assetsDetails(sponsoredAssetId).minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) + sponsoredAssetId = stateChanges.sponsorFees.head.assetId + miner.assetInfo(sponsoredAssetId).sponsorship shouldBe minSponsoredAssetFee - val dAppBalance = miner.assetsBalance(globalDAppAddress).balances.head - dAppBalance.minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) - dAppBalance.sponsorBalance shouldBe Some(miner.balance(globalDAppAddress).balance) + val (assetId, _) = miner.assetsBalance(globalDAppAddress).filter(_._1.nonEmpty).head + val assetInfo = miner.assetInfo(assetId) + assetInfo.sponsorship shouldBe minSponsoredAssetFee + assetInfo.sponsorBalance shouldBe miner.wavesBalance(globalDAppAddress).regular } "Use sponsored asset as fee" in { - nodes.waitForHeight(miner.height + 1) + nodes.foreach(_.waitForHeight(miner.height + 1)) - val alice = miner.createKeyPair() - val bob = miner.createKeyPair() + val alice = miner.generateKeyPair() + val bob = miner.generateKeyPair() - val dAppAddress = globalDAppAddress - val startDAppSponsorAssetBalance = miner.assetBalance(dAppAddress, sponsoredAssetId).balance - val startDAppBalance = miner.balance(dAppAddress).balance - val startMinerBalance = miner.balance(miner.address).balance + val (_, startDAppSponsorAssetBalance) = miner.assetsBalance(globalDAppAddress, Seq(sponsoredAssetId)).head + val startDAppBalance = miner.wavesBalance(globalDAppAddress).regular val assetFee = 100 val assetTransferAmount = 1000 - miner.transfer( + miner.broadcastTransfer( dApp, - alice.toAddress.toString, + PBRecipients.create(alice.toAddress), assetFee + assetTransferAmount, - assetId = Some(sponsoredAssetId), + assetId = sponsoredAssetId, fee = smartMinFee, waitForTx = true ) - miner.transfer( + miner.broadcastTransfer( alice, - bob.toAddress.toString, + PBRecipients.create(bob.toAddress), assetTransferAmount, - assetId = Some(sponsoredAssetId), + assetId = sponsoredAssetId, fee = assetFee, - feeAssetId = Some(sponsoredAssetId), + feeAssetId = sponsoredAssetId, waitForTx = true ) - miner.assetBalance(alice.toAddress.toString, sponsoredAssetId).balance shouldBe 0 - miner.assetBalance(bob.toAddress.toString, sponsoredAssetId).balance shouldBe assetTransferAmount + miner.assetsBalance(byteStringAddress(alice), Seq(sponsoredAssetId)).head._2 shouldBe 0 + miner.assetsBalance(byteStringAddress(bob), Seq(sponsoredAssetId)).head._2 shouldBe assetTransferAmount val dAppWavesOutgo = smartMinFee + Sponsorship.toWaves(assetFee, minSponsoredAssetFee) - val blockReward = miner.lastBlock().reward.get miner.waitForHeight(miner.height + 1) - miner.assetBalance(dAppAddress, sponsoredAssetId).balance shouldBe startDAppSponsorAssetBalance - assetTransferAmount - miner.balance(dAppAddress).balance shouldBe startDAppBalance - dAppWavesOutgo - miner.balance(miner.address).balance shouldBe startMinerBalance + dAppWavesOutgo + blockReward + miner.assetsBalance(globalDAppAddress, Seq(sponsoredAssetId)).head._2 shouldBe startDAppSponsorAssetBalance - assetTransferAmount + miner.wavesBalance(globalDAppAddress).regular shouldBe startDAppBalance - dAppWavesOutgo } "Cancel sponsorship" in { @@ -154,35 +159,48 @@ class GrpcSponsorFeeActionSuite extends AnyFreeSpec with GrpcBaseTransactionSuit """.stripMargin ) - val dAppAddress = dApp.toAddress.toString - val issueTx = miner.invokeScript(miner.keyPair, dAppAddress, Some("issue2assets"), waitForTx = true, fee = smartMinFee + issueFee * 2) - miner.invokeScript(miner.keyPair, dAppAddress, Some("sponsor2assets"), waitForTx = true, fee = smartMinFee) - val cancelTx = miner.invokeScript(miner.keyPair, dAppAddress, Some("cancelSponsorship"), waitForTx = true, fee = smartMinFee) + val dAppAddress = byteStringAddress(dApp) + val issueTx = miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User("issue2assets"), List.empty)), + waitForTx = true, + fee = smartMinFee + issueFee * 2 + ) + miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User("sponsor2assets"), List.empty)), + waitForTx = true, + fee = smartMinFee + ) + val cancelTx = miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User("cancelSponsorship"), List.empty)), + waitForTx = true, + fee = smartMinFee + ) - val assetIdByName = miner.debugStateChanges(issueTx._1.id).stateChanges.get.issues.map(issue => (issue.name, issue.assetId)).toMap + val assetIdByName = miner.stateChanges(issueTx.id)._2.issues.map(issue => (issue.name, issue.assetId)).toMap val sponsoredAssetId = assetIdByName("SponsoredAsset1") val cancelledAssetId = assetIdByName("SponsoredAsset0") - miner - .debugStateChanges(cancelTx._1.id) - .stateChanges - .toSeq - .find(_.sponsorFees == Seq(SponsorFeeResponse(`cancelledAssetId`, None))) - .head - - miner - .debugStateChangesByAddress(dAppAddress, limit = 100) - .flatMap(_.stateChanges) - .find(_.sponsorFees == Seq(SponsorFeeResponse(`cancelledAssetId`, None))) - .head - - miner.assetsDetails(sponsoredAssetId).minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) - - val dAppBalance = miner.assetsBalance(dAppAddress).balances.map(b => (b.assetId, b)).toMap - dAppBalance(sponsoredAssetId).minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) - dAppBalance(sponsoredAssetId).sponsorBalance shouldBe Some(miner.balance(dAppAddress).balance) - dAppBalance(cancelledAssetId).minSponsoredAssetFee shouldBe None - dAppBalance(cancelledAssetId).sponsorBalance shouldBe None + miner.stateChanges(cancelTx.id)._2.sponsorFees shouldBe Seq(SponsorFeeResponse(cancelledAssetId, None)) + miner.stateChanges(minerAddress).map(_._2).find(_.sponsorFees == Seq(SponsorFeeResponse(cancelledAssetId, None))) shouldBe defined + + val dAppBalance = miner.assetsBalance(dAppAddress) + + dAppBalance.contains(sponsoredAssetId) shouldBe true + dAppBalance.contains(cancelledAssetId) shouldBe true + + val sponsoredAsset = miner.assetInfo(sponsoredAssetId) + sponsoredAsset.sponsorship shouldBe minSponsoredAssetFee + sponsoredAsset.sponsorBalance shouldBe miner.wavesBalance(dAppAddress).regular + + val cancelledAsset = miner.assetInfo(cancelledAssetId) + cancelledAsset.sponsorship shouldBe 0 + cancelledAsset.sponsorBalance shouldBe 0 } "Multiple SponsorFee is available for same asset" in { @@ -212,33 +230,38 @@ class GrpcSponsorFeeActionSuite extends AnyFreeSpec with GrpcBaseTransactionSuit """.stripMargin ) - val dAppAddress = dApp.toAddress.toString - val invokeTx = miner.invokeScript(miner.keyPair, dAppAddress, Some("issueAndMultipleSponsor"), waitForTx = true, fee = smartMinFee + issueFee) - val txStateChanges = miner.debugStateChanges(invokeTx._1.id).stateChanges.toSeq - val assetId = txStateChanges.flatMap(_.issues).head.assetId + val dAppAddress = byteStringAddress(dApp) + val invokeTx = miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User("issueAndMultipleSponsor"), List.empty)), + waitForTx = true, + fee = smartMinFee + issueFee + ) + + val txStateChanges = miner.stateChanges(invokeTx.id)._2 + val assetId = txStateChanges.issues.head.assetId val matchDebugResult = matchPattern { - case Seq( - StateChangesDetails( - Nil, - Nil, - Seq(IssueInfoResponse(`assetId`, _, _, _, _, _, _, _)), - Nil, - Nil, - sponsorFeeResponses, - None, - Nil - ) + case StateChangesDetails( + Nil, + Nil, + Seq(IssueInfoResponse(`assetId`, _, _, _, _, _, _, _)), + Nil, + Nil, + sponsorFeeResponses, + None, + Nil ) if sponsorFeeResponses.size == 9 && sponsorFeeResponses.last == SponsorFeeResponse(`assetId`, Some(`lastMinSponsoredAssetFee`)) => } txStateChanges should matchDebugResult - miner.debugStateChangesByAddress(dAppAddress, limit = 100).flatMap(_.stateChanges) should matchDebugResult + miner.stateChanges(minerAddress).map(_._2).head should matchDebugResult - miner.assetsDetails(assetId).minSponsoredAssetFee shouldBe Some(lastMinSponsoredAssetFee) + miner.assetsBalance(dAppAddress).contains(assetId) shouldBe true - val dAppBalance = miner.assetsBalance(dAppAddress).balances.head - dAppBalance.minSponsoredAssetFee shouldBe Some(lastMinSponsoredAssetFee) - dAppBalance.sponsorBalance shouldBe Some(miner.balance(dAppAddress).balance) + val assetInfo = miner.assetInfo(assetId) + assetInfo.sponsorship shouldBe lastMinSponsoredAssetFee + assetInfo.sponsorBalance shouldBe miner.wavesBalance(dAppAddress).regular } "Sponsor and cancel sponsorship is available for same asset" in { @@ -260,146 +283,46 @@ class GrpcSponsorFeeActionSuite extends AnyFreeSpec with GrpcBaseTransactionSuit """.stripMargin ) + val dAppAddress = byteStringAddress(dApp) val invokeTx = - miner.invokeScript(miner.keyPair, dApp.toAddress.toString, Some("sponsorAndCancel"), waitForTx = true, fee = smartMinFee + issueFee) - val txStateChanges = miner.debugStateChanges(invokeTx._1.id).stateChanges.toSeq - val assetId = txStateChanges.flatMap(_.issues).head.assetId + miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User("sponsorAndCancel"), List.empty)), + waitForTx = true, + fee = smartMinFee + issueFee + ) + val txStateChanges = miner.stateChanges(invokeTx.id)._2 + val assetId = txStateChanges.issues.head.assetId val matchDebugResult = matchPattern { - case Seq( - StateChangesDetails( - Nil, - Nil, - Seq(IssueInfoResponse(`assetId`, _, _, _, _, _, _, _)), - Nil, - Nil, - Seq(SponsorFeeResponse(`assetId`, Some(100)), SponsorFeeResponse(`assetId`, None)), - None, - Nil - ) + case StateChangesDetails( + Nil, + Nil, + Seq(IssueInfoResponse(`assetId`, _, _, _, _, _, _, _)), + Nil, + Nil, + Seq(SponsorFeeResponse(`assetId`, Some(100)), SponsorFeeResponse(`assetId`, None)), + None, + Nil ) => } txStateChanges should matchDebugResult - miner.debugStateChangesByAddress(dApp.toAddress.toString, limit = 100).flatMap(_.stateChanges) should matchDebugResult + miner.stateChanges(minerAddress).map(_._2).head should matchDebugResult - miner.assetsDetails(assetId).minSponsoredAssetFee shouldBe None + miner.assetsBalance(dAppAddress).contains(assetId) shouldBe true - val dAppBalance = miner.assetsBalance(dApp.toAddress.toString).balances.head - dAppBalance.minSponsoredAssetFee shouldBe None - dAppBalance.sponsorBalance shouldBe None + val assetInfo = miner.assetInfo(assetId) + assetInfo.sponsorship shouldBe 0 + assetInfo.sponsorBalance shouldBe 0 } } "Restrictions" - { - "SponsorFee actions count limit" in { - val minSponsoredAssetFee = 1001 - - val dApp = createDApp( - s""" - |{-# STDLIB_VERSION 4 #-} - |{-# CONTENT_TYPE DAPP #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - | - |@Callable(i) - |func issue10assets() = { - | let i0 = Issue("SponsoredAsset0", "SponsoredAsset description", 1000000000000000, 2, true, unit, 0) - | let i1 = Issue("SponsoredAsset1", "SponsoredAsset description", 1000000000000000, 2, true, unit, 1) - | let i2 = Issue("SponsoredAsset2", "SponsoredAsset description", 1000000000000000, 2, true, unit, 2) - | let i3 = Issue("SponsoredAsset3", "SponsoredAsset description", 1000000000000000, 2, true, unit, 3) - | let i4 = Issue("SponsoredAsset4", "SponsoredAsset description", 1000000000000000, 2, true, unit, 4) - | let i5 = Issue("SponsoredAsset5", "SponsoredAsset description", 1000000000000000, 2, true, unit, 5) - | let i6 = Issue("SponsoredAsset6", "SponsoredAsset description", 1000000000000000, 2, true, unit, 6) - | let i7 = Issue("SponsoredAsset7", "SponsoredAsset description", 1000000000000000, 2, true, unit, 7) - | let i8 = Issue("SponsoredAsset8", "SponsoredAsset description", 1000000000000000, 2, true, unit, 8) - | let i9 = Issue("SponsoredAsset9", "SponsoredAsset description", 1000000000000000, 2, true, unit, 9) - | - | let issueId0 = calculateAssetId(i0) - | let issueId1 = calculateAssetId(i1) - | let issueId2 = calculateAssetId(i2) - | let issueId3 = calculateAssetId(i3) - | let issueId4 = calculateAssetId(i4) - | let issueId5 = calculateAssetId(i5) - | let issueId6 = calculateAssetId(i6) - | let issueId7 = calculateAssetId(i7) - | let issueId8 = calculateAssetId(i8) - | let issueId9 = calculateAssetId(i9) - | - | [ - | BinaryEntry("sponsoredAssetId0", issueId0), - | BinaryEntry("sponsoredAssetId1", issueId1), - | BinaryEntry("sponsoredAssetId2", issueId2), - | BinaryEntry("sponsoredAssetId3", issueId3), - | BinaryEntry("sponsoredAssetId4", issueId4), - | BinaryEntry("sponsoredAssetId5", issueId5), - | BinaryEntry("sponsoredAssetId6", issueId6), - | BinaryEntry("sponsoredAssetId7", issueId7), - | BinaryEntry("sponsoredAssetId8", issueId8), - | BinaryEntry("sponsoredAssetId9", issueId9), - | i0,i1,i2,i3,i4,i5,i6,i7,i8,i9 - | ] - |} - | - |@Callable(i) - |func sponsor10assets() = [ - | SponsorFee(this.getBinary("sponsoredAssetId0").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId1").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId2").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId3").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId4").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId5").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId6").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId7").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId8").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId9").value(), ${minSponsoredAssetFee.toString}) - |] - | - |@Callable(i) - |func sponsor11assets() = [ - | SponsorFee(this.getBinary("sponsoredAssetId0").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId1").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId2").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId3").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId4").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId5").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId6").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId7").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId8").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId9").value(), ${minSponsoredAssetFee.toString}), - | SponsorFee(this.getBinary("sponsoredAssetId9").value(), ${minSponsoredAssetFee.toString}) - |] - """.stripMargin - ) - val dappAddress = dApp.toAddress.toString - val invokeTx1 = miner.invokeScript(miner.keyPair, dappAddress, Some("issue10assets"), waitForTx = true, fee = smartMinFee + issueFee * 10) - val invokeTx2 = miner.invokeScript(miner.keyPair, dappAddress, Some("sponsor10assets"), waitForTx = true, fee = smartMinFee) - - nodes.waitForHeightAriseAndTxPresent(invokeTx1._1.id) - nodes.waitForHeightAriseAndTxPresent(invokeTx2._1.id) - - val assetIds = miner.debugStateChanges(invokeTx1._1.id).stateChanges.get.issues.map(_.assetId) - val sponsorFees = miner.debugStateChanges(invokeTx2._1.id).stateChanges.get.sponsorFees - - (assetIds zip sponsorFees) - .foreach { case (issueAssetId, sponsorFee) => - issueAssetId shouldBe sponsorFee.assetId - sponsorFee.minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) - - miner.assetsDetails(issueAssetId).minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) - val dAppBalance = miner.assetsBalance(dappAddress).balances.find(_.assetId == issueAssetId).get - dAppBalance.minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) - dAppBalance.sponsorBalance shouldBe Some(miner.balance(dappAddress).balance) - } - - assertBadRequestAndMessage( - miner.invokeScript(miner.keyPair, dappAddress, Some("sponsor11assets"), fee = smartMinFee), - "Actions count limit is exceeded" - ) - } - "SponsorFee is available for assets issued via transaction" in { - val dApp = miner.createKeyPair() - miner.transfer(sender.keyPair, dApp.toAddress.toString, initialWavesBalance, minFee, waitForTx = true) - val assetId = miner.issue(dApp, waitForTx = true).id + val dApp = miner.generateKeyPair() + miner.broadcastTransfer(sender.keyPair, PBRecipients.create(dApp.toAddress), initialWavesBalance, minFee, waitForTx = true) + val assetId = miner.broadcastIssue(dApp, "test", 100, 8, reissuable = true, fee = issueFee, waitForTx = true).id createDApp( s""" @@ -415,14 +338,20 @@ class GrpcSponsorFeeActionSuite extends AnyFreeSpec with GrpcBaseTransactionSuit dApp ) - val tx = miner.invokeScript(miner.keyPair, dApp.toAddress.toString, Some("sponsorAsset"), waitForTx = true, fee = smartMinFee) - sender.debugStateChanges(tx._1.id).stateChanges.get.sponsorFees.head shouldBe SponsorFeeResponse(assetId, Some(1000)) + val tx = miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User("sponsorAsset"), List.empty)), + waitForTx = true, + fee = smartMinFee + ) + sender.stateChanges(tx.id)._2.sponsorFees.head shouldBe SponsorFeeResponse(assetId, Some(1000)) } "Negative fee is not available" in { - val dApp = miner.createKeyPair() - miner.transfer(sender.keyPair, dApp.toAddress.toString, initialWavesBalance, minFee, waitForTx = true) - val assetId = miner.issue(dApp, waitForTx = true).id + val dApp = miner.generateKeyPair() + miner.broadcastTransfer(sender.keyPair, PBRecipients.create(dApp.toAddress), initialWavesBalance, minFee, waitForTx = true) + val assetId = miner.broadcastIssue(dApp, "test", 100, 8, reissuable = true, fee = issueFee, waitForTx = true).id createDApp( s""" @@ -438,16 +367,22 @@ class GrpcSponsorFeeActionSuite extends AnyFreeSpec with GrpcBaseTransactionSuit dApp ) - assertBadRequestAndMessage( - miner.invokeScript(miner.keyPair, dApp.toAddress.toString, Some("sponsorAsset"), waitForTx = true, fee = smartMinFee), + assertGrpcError( + miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User("sponsorAsset"), List.empty)), + waitForTx = true, + fee = smartMinFee + ), "Negative sponsor amount = -1" ) } "SponsorFee is available only for assets issuing from current address" in { - val issuer = miner.createKeyPair() - miner.transfer(sender.keyPair, issuer.toAddress.toString, initialWavesBalance, minFee, waitForTx = true) - val assetId = miner.issue(issuer, waitForTx = true).id + val issuer = miner.generateKeyPair() + miner.broadcastTransfer(sender.keyPair, PBRecipients.create(issuer.toAddress), initialWavesBalance, minFee, waitForTx = true) + val assetId = miner.broadcastIssue(issuer, "test", 100, 8, reissuable = true, fee = issueFee, waitForTx = true).id val dApp = createDApp( s""" @@ -460,20 +395,26 @@ class GrpcSponsorFeeActionSuite extends AnyFreeSpec with GrpcBaseTransactionSuit | SponsorFee(base58'$assetId', 1000) |] """.stripMargin - ).toAddress.toString + ) - assertBadRequestAndMessage( - miner.invokeScript(miner.keyPair, dApp, Some("sponsorAsset"), waitForTx = true, fee = smartMinFee), + assertGrpcError( + miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User("sponsorAsset"), List.empty)), + waitForTx = true, + fee = smartMinFee + ), s"SponsorFee assetId=$assetId was not issued from address of current dApp" ) } "SponsorFee is not available for scripted assets" in { - val dApp = miner.createKeyPair() - miner.transfer(sender.keyPair, dApp.toAddress.toString, initialWavesBalance, minFee, waitForTx = true) + val dApp = miner.generateKeyPair() + miner.broadcastTransfer(sender.keyPair, PBRecipients.create(dApp.toAddress), initialWavesBalance, minFee, waitForTx = true) - val script = ScriptCompiler.compile("true", ScriptEstimatorV2).explicitGet()._1.bytes().base64 - val assetId = miner.issue(dApp, script = Some(script), waitForTx = true).id + val script = ScriptCompiler.compile("true", ScriptEstimatorV2).explicitGet()._1 + val assetId = miner.broadcastIssue(dApp, "test", 100, 8, reissuable = true, script = Right(Some(script)), fee = issueFee, waitForTx = true).id createDApp( s""" @@ -489,14 +430,19 @@ class GrpcSponsorFeeActionSuite extends AnyFreeSpec with GrpcBaseTransactionSuit dApp ) - assertBadRequestAndMessage( - miner.invokeScript(miner.keyPair, dApp.toAddress.toString, Some("sponsorAsset"), fee = smartMinFee + smartFee), + assertGrpcError( + miner.broadcastInvokeScript( + miner.keyPair, + PBRecipients.create(dApp.toAddress), + Some(FUNCTION_CALL(FunctionHeader.User("sponsorAsset"), List.empty)), + fee = smartMinFee + smartFee + ), "Sponsorship smart assets is disabled." ) } } - private def createDApp(script: String, address: KeyPair = miner.createKeyPair()): KeyPair = { + private def createDApp(script: String, address: KeyPair = miner.generateKeyPair()): KeyPair = { val compiledScript = ScriptCompiler .compile( script, @@ -505,19 +451,27 @@ class GrpcSponsorFeeActionSuite extends AnyFreeSpec with GrpcBaseTransactionSuit .explicitGet() ._1 - miner.transfer(sender.keyPair, address.publicKey.toAddress.toString, initialWavesBalance, minFee, waitForTx = true) + miner.broadcastTransfer( + sender.keyPair, + PBRecipients.create(address.toAddress), + initialWavesBalance, + fee = minFee, + waitForTx = true + ) - nodes.waitForHeightAriseAndTxPresent( - miner - .signedBroadcast( - SetScriptTransaction - .selfSigned(1.toByte, address, Some(compiledScript), setScriptFee, System.currentTimeMillis()) - .explicitGet() - .json() - ) - .id + nodes.foreach( + _.waitForTxAndHeightArise( + miner + .setScript(address, Right(Some(compiledScript)), fee = smartMinFee + setScriptFee, timestamp = System.currentTimeMillis(), waitForTx = true) + .id + ) ) address } + + private def byteStringAddress(account: KeyPair): ByteString = + PBRecipients.create(account.toAddress).getPublicKeyHash + + private def minerAddress = byteStringAddress(miner.keyPair) } diff --git a/node-it/src/test/scala/com/wavesplatform/it/asset/HttpScriptAssetActionLimitsSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/asset/HttpScriptAssetActionLimitsSuite.scala new file mode 100644 index 00000000000..8bacae1efef --- /dev/null +++ b/node-it/src/test/scala/com/wavesplatform/it/asset/HttpScriptAssetActionLimitsSuite.scala @@ -0,0 +1,141 @@ +package com.wavesplatform.it.asset + +import com.wavesplatform.account.KeyPair +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.it.api.SyncHttpApi.* +import com.wavesplatform.it.sync.{issueFee, minFee, smartMinFee} +import com.wavesplatform.lang.directives.values.{V4, V5, V6} +import com.wavesplatform.lang.v1.ContractLimits +import com.wavesplatform.lang.v1.compiler.Terms.CONST_BYTESTR +import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 +import com.wavesplatform.test.* +import com.wavesplatform.transaction.smart.SetScriptTransaction +import com.wavesplatform.transaction.smart.script.ScriptCompiler + +class HttpScriptAssetActionLimitsSuite extends ScriptAssetActionLimitsSuite { + + def createDApp(script: String, address: KeyPair = miner.createKeyPair()): KeyPair = { + val compiledScript = ScriptCompiler + .compile( + script, + ScriptEstimatorV2 + ) + .explicitGet() + ._1 + + miner.transfer(sender.keyPair, address.publicKey.toAddress.toString, initialWavesBalance, minFee, waitForTx = true) + + nodes.waitForHeightAriseAndTxPresent( + miner + .signedBroadcast( + SetScriptTransaction + .selfSigned(1.toByte, address, Some(compiledScript), 1.waves, System.currentTimeMillis()) + .explicitGet() + .json() + ) + .id + ) + + address + } + + Seq(V4, V5, V6).foreach { version => + val actionsLimit = + if (version == V6) + ContractLimits.MaxAssetScriptActionsAmountV6 + else + ContractLimits.MaxCallableActionsAmountBeforeV6(version) + + val errMsg = + if (version == V6) + "Issue, Reissue, Burn, SponsorFee actions count limit is exceeded" + else + "Actions count limit is exceeded" + + s"SponsorFee actions count limit for V${version.id}" in { + val dApp = createSponsorFeeDApp(actionsLimit, version) + val dAppAddress = dApp.toAddress.toString + val invokeTx1 = miner.invokeScript( + miner.keyPair, + dAppAddress, + Some(s"issue${actionsLimit}assets"), + waitForTx = true, + fee = smartMinFee + issueFee * actionsLimit + ) + val invokeTx2 = miner.invokeScript(miner.keyPair, dAppAddress, Some(s"sponsor${actionsLimit}assets"), waitForTx = true, fee = smartMinFee) + + val assetIds = miner.debugStateChanges(invokeTx1._1.id).stateChanges.get.issues.map(_.assetId) + val sponsorFees = miner.debugStateChanges(invokeTx2._1.id).stateChanges.get.sponsorFees + + (assetIds zip sponsorFees) + .foreach { case (issueAssetId, sponsorFee) => + issueAssetId shouldBe sponsorFee.assetId + sponsorFee.minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) + + miner.assetsDetails(issueAssetId).minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) + val dAppBalance = miner.assetsBalance(dAppAddress).balances.find(_.assetId == issueAssetId).get + dAppBalance.minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) + dAppBalance.sponsorBalance shouldBe Some(miner.balance(dAppAddress).balance) + } + + assertBadRequestAndMessage( + miner.invokeScript(miner.keyPair, dAppAddress, Some(s"sponsor${actionsLimit + 1}assets"), fee = smartMinFee), + errMsg + ) + } + + s"Issue $actionsLimit assets should not produce an error for V${version.id}" in { + val acc = createDApp(script(actionsLimit, version)) + val (tx, _) = miner.invokeScript( + acc, + acc.toAddress.toString, + Some(s"issue${actionsLimit}Assets"), + fee = smartMinFee + issueFee * actionsLimit, + waitForTx = true + ) + for (nth <- 0 until actionsLimit) { + val assetInfo = sender.debugStateChanges(tx.id).stateChanges.get.issues(nth) + assetInfo.quantity shouldBe asset.quantity + assetInfo.name shouldBe asset.name + assetInfo.description shouldBe asset.description + assetInfo.decimals shouldBe asset.decimals + assetInfo.isReissuable shouldBe asset.reissuable + sender.assertAssetBalance(acc.toAddress.toString, assetInfo.assetId, asset.quantity) + } + } + + s"More than $actionsLimit Issue/Reissue/Burn/SponsorFee actions should produce an error for V${version.id}" in { + val acc = createDApp(script(actionsLimit, version)) + val issue = miner.issue(acc) + + assertApiError( + miner.invokeScript( + acc, + acc.toAddress.toString, + Some(s"process${actionsLimit + 1}actions"), + List(CONST_BYTESTR(ByteStr.decodeBase58(issue.id).get).explicitGet()), + fee = smartMinFee + issueFee * (actionsLimit + 1), + waitForTx = true + ) + ) { e => + e.message should include(errMsg) + } + } + + s"More than $actionsLimit issue actions should produce an error for V${version.id}" in { + val acc = createDApp(script(actionsLimit, version)) + assertApiError( + miner.invokeScript( + acc, + acc.toAddress.toString, + Some(s"issue${actionsLimit + 1}Assets"), + fee = smartMinFee + issueFee * (actionsLimit + 1), + waitForTx = true + ) + ) { e => + e.message should include(errMsg) + } + } + } +} diff --git a/node-it/src/test/scala/com/wavesplatform/it/asset/ScriptAssetActionLimitsSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/asset/ScriptAssetActionLimitsSuite.scala index 69969a5c4d9..9751939735b 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/asset/ScriptAssetActionLimitsSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/asset/ScriptAssetActionLimitsSuite.scala @@ -2,159 +2,27 @@ package com.wavesplatform.it.asset import com.typesafe.config.Config import com.wavesplatform.account.KeyPair -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.features.BlockchainFeatures.{RideV6, SynchronousCalls} -import com.wavesplatform.it.NodeConfigs +import com.wavesplatform.it.{BaseFreeSpec, NodeConfigs} import com.wavesplatform.it.NodeConfigs.Default -import com.wavesplatform.it.api.SyncHttpApi.* -import com.wavesplatform.it.sync.grpc.GrpcBaseTransactionSuiteLike -import com.wavesplatform.it.sync.{issueFee, minFee, smartMinFee} -import com.wavesplatform.lang.directives.values.{StdLibVersion, V4, V5, V6} -import com.wavesplatform.lang.v1.ContractLimits -import com.wavesplatform.lang.v1.compiler.Terms.CONST_BYTESTR -import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 +import com.wavesplatform.lang.directives.values.StdLibVersion import com.wavesplatform.test.* -import com.wavesplatform.transaction.smart.SetScriptTransaction -import com.wavesplatform.transaction.smart.script.ScriptCompiler -import org.scalatest.freespec.AnyFreeSpec -class ScriptAssetActionLimitsSuite extends AnyFreeSpec with GrpcBaseTransactionSuiteLike { +trait ScriptAssetActionLimitsSuite extends BaseFreeSpec { + + def createDApp(script: String, address: KeyPair = miner.generateKeyPair()): KeyPair + override protected def nodeConfigs: Seq[Config] = NodeConfigs .Builder(Default, 2, Seq.empty) .overrideBase(_.preactivatedFeatures((SynchronousCalls.id, 0), (RideV6.id, 0))) .buildNonConflicting() - private val initialWavesBalance = 1000.waves - private val minSponsoredAssetFee = 1001 - private val asset = Asset("Simple", "ReissuableAsset", "description", 100000000, reissuable = true, 3, 0) - - Seq(V4, V5, V6).foreach { version => - val actionsLimit = - if (version == V6) - ContractLimits.MaxAssetScriptActionsAmountV6 - else - ContractLimits.MaxCallableActionsAmountBeforeV6(version) - - val errMsg = - if (version == V6) - "Issue, Reissue, Burn, SponsorFee actions count limit is exceeded" - else - "Actions count limit is exceeded" - - s"SponsorFee actions count limit for V${version.id}" in { - val dApp = createSponsorFeeDApp(actionsLimit, version) - val dAppAddress = dApp.toAddress.toString - val invokeTx1 = miner.invokeScript( - miner.keyPair, - dAppAddress, - Some(s"issue${actionsLimit}assets"), - waitForTx = true, - fee = smartMinFee + issueFee * actionsLimit - ) - val invokeTx2 = miner.invokeScript(miner.keyPair, dAppAddress, Some(s"sponsor${actionsLimit}assets"), waitForTx = true, fee = smartMinFee) - - val assetIds = miner.debugStateChanges(invokeTx1._1.id).stateChanges.get.issues.map(_.assetId) - val sponsorFees = miner.debugStateChanges(invokeTx2._1.id).stateChanges.get.sponsorFees - - (assetIds zip sponsorFees) - .foreach { case (issueAssetId, sponsorFee) => - issueAssetId shouldBe sponsorFee.assetId - sponsorFee.minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) - - miner.assetsDetails(issueAssetId).minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) - val dAppBalance = miner.assetsBalance(dAppAddress).balances.find(_.assetId == issueAssetId).get - dAppBalance.minSponsoredAssetFee shouldBe Some(minSponsoredAssetFee) - dAppBalance.sponsorBalance shouldBe Some(miner.balance(dAppAddress).balance) - } - - assertBadRequestAndMessage( - miner.invokeScript(miner.keyPair, dAppAddress, Some(s"sponsor${actionsLimit + 1}assets"), fee = smartMinFee), - errMsg - ) - } - - s"Issue $actionsLimit assets should not produce an error for V${version.id}" in { - val acc = createDApp(script(actionsLimit, version)) - val (tx, _) = miner.invokeScript( - acc, - acc.toAddress.toString, - Some(s"issue${actionsLimit}Assets"), - fee = smartMinFee + issueFee * actionsLimit, - waitForTx = true - ) - for (nth <- 0 until actionsLimit) { - val assetInfo = sender.debugStateChanges(tx.id).stateChanges.get.issues(nth) - assetInfo.quantity shouldBe asset.quantity - assetInfo.name shouldBe asset.name - assetInfo.description shouldBe asset.description - assetInfo.decimals shouldBe asset.decimals - assetInfo.isReissuable shouldBe asset.reissuable - sender.assertAssetBalance(acc.toAddress.toString, assetInfo.assetId, asset.quantity) - } - } - - s"More than $actionsLimit Issue/Reissue/Burn/SponsorFee actions should produce an error for V${version.id}" in { - val acc = createDApp(script(actionsLimit, version)) - val issue = miner.issue(acc) - - assertApiError( - miner.invokeScript( - acc, - acc.toAddress.toString, - Some(s"process${actionsLimit + 1}actions"), - List(CONST_BYTESTR(ByteStr.decodeBase58(issue.id).get).explicitGet()), - fee = smartMinFee + issueFee * (actionsLimit + 1), - waitForTx = true - ) - ) { e => - e.message should include(errMsg) - } - } - - s"More than $actionsLimit issue actions should produce an error for V${version.id}" in { - val acc = createDApp(script(actionsLimit, version)) - assertApiError( - miner.invokeScript( - acc, - acc.toAddress.toString, - Some(s"issue${actionsLimit + 1}Assets"), - fee = smartMinFee + issueFee * (actionsLimit + 1), - waitForTx = true - ) - ) { e => - e.message should include(errMsg) - } - } - } - - private def createDApp(script: String, address: KeyPair = miner.createKeyPair()): KeyPair = { - val compiledScript = ScriptCompiler - .compile( - script, - ScriptEstimatorV2 - ) - .explicitGet() - ._1 - - miner.transfer(sender.keyPair, address.publicKey.toAddress.toString, initialWavesBalance, minFee, waitForTx = true) - - nodes.waitForHeightAriseAndTxPresent( - miner - .signedBroadcast( - SetScriptTransaction - .selfSigned(1.toByte, address, Some(compiledScript), 1.waves, System.currentTimeMillis()) - .explicitGet() - .json() - ) - .id - ) - - address - } + protected val initialWavesBalance: Long = 1000.waves + protected val minSponsoredAssetFee: Long = 1001 + protected val asset: Asset = Asset("Simple", "ReissuableAsset", "description", 100000000, reissuable = true, 3, 0) - private def createSponsorFeeDApp(actionsLimit: Int, version: StdLibVersion) = + protected def createSponsorFeeDApp(actionsLimit: Int, version: StdLibVersion): KeyPair = createDApp( s""" |{-# STDLIB_VERSION ${version.id} #-} @@ -196,7 +64,7 @@ class ScriptAssetActionLimitsSuite extends AnyFreeSpec with GrpcBaseTransactionS """.stripMargin ) - def script(actionsLimit: Int, version: StdLibVersion): String = { + protected def script(actionsLimit: Int, version: StdLibVersion): String = { def createIssueParams(asset: Asset) = s""""${asset.name}","${asset.description}",${asset.quantity}, ${asset.decimals},${asset.reissuable},unit""" val issueParams = createIssueParams(asset)