From 39070eccfcaec54537ba8a192ee5b084f27459e3 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Thu, 13 Jan 2022 17:11:15 +0300 Subject: [PATCH] NODE-2369: Add the old price mode for OrderV4 (#3599) --- .scalafmt.conf | 4 +- .../protobuf/SmartNoSmartBenchmark.scala | 28 +- .../api/grpc/test/GRPCBroadcastSpec.scala | 37 +- .../events/BlockchainUpdatesSpec.scala | 243 ++++++----- .../AcceptFailedScriptActivationSuite.scala | 14 +- .../ExchangeTransactionSuite.scala | 80 ++-- .../FailedTransactionSuiteLike.scala | 140 ++++--- .../utils/TransactionSerializeSuite.scala | 42 +- .../protobuf/transaction/PBOrders.scala | 40 +- .../state/diffs/CommonValidation.scala | 16 +- .../state/diffs/ExchangeTransactionDiff.scala | 54 ++- .../assets/exchange/EthOrders.scala | 188 +++++---- .../transaction/assets/exchange/Order.scala | 101 +++-- .../assets/exchange/OrderJson.scala | 84 ++-- .../assets/exchange/OrderPriceMode.scala | 30 ++ .../serialization/impl/OrderSerializer.scala | 40 +- .../com/wavesplatform/TransactionGen.scala | 119 ++++-- .../http/TransactionBroadcastSpec.scala | 189 ++++----- .../diffs/ExchangeTransactionDiffTest.scala | 233 ++++++++--- .../ExchangeTransactionSpecification.scala | 388 +++++++++--------- .../transaction/OrderSpecification.scala | 119 +++--- .../wavesplatform/transaction/TxHelpers.scala | 4 + .../assets/exchange/EthOrderSpec.scala | 269 ++++++++---- .../exchange/OrderJsonSpecification.scala | 91 +++- .../com/wavesplatform/utils/EthHelpers.scala | 7 +- 25 files changed, 1508 insertions(+), 1052 deletions(-) create mode 100644 node/src/main/scala/com/wavesplatform/transaction/assets/exchange/OrderPriceMode.scala diff --git a/.scalafmt.conf b/.scalafmt.conf index d41be7d9c12..3b25e83d485 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,3 +1,5 @@ -version = "2.0.1" +version = "3.2.2" +runner.dialect = scala213source3 style = defaultWithAlign +assumeStandardLibraryStripMargin = true maxColumn = 150 diff --git a/benchmark/src/test/scala/com/wavesplatform/serialization/protobuf/SmartNoSmartBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/serialization/protobuf/SmartNoSmartBenchmark.scala index 30ab17b1b80..51419433561 100644 --- a/benchmark/src/test/scala/com/wavesplatform/serialization/protobuf/SmartNoSmartBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/serialization/protobuf/SmartNoSmartBenchmark.scala @@ -37,33 +37,9 @@ class SmartNoSmartBenchmark { object SmartNoSmartBenchmark { @State(Scope.Benchmark) class ExchangeTransactionSt { - val buy = Order( - TxVersion.V2, - PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet(), - PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), - AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, - OrderType.BUY, - 2, - 6000000000L, - 1526992336241L, - 1529584336241L, - 1, - proofs = Proofs(Seq(ByteStr.decodeBase58("2bkuGwECMFGyFqgoHV4q7GRRWBqYmBFWpYRkzgYANR4nN2twgrNaouRiZBqiK2RJzuo9NooB9iRiuZ4hypBbUQs").get)) - ) + val buy = Order(TxVersion.V2, PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet(), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.BUY, 2, 6000000000L, 1526992336241L, 1529584336241L, 1, proofs = Proofs(Seq(ByteStr.decodeBase58("2bkuGwECMFGyFqgoHV4q7GRRWBqYmBFWpYRkzgYANR4nN2twgrNaouRiZBqiK2RJzuo9NooB9iRiuZ4hypBbUQs").get))) - val sell = Order( - TxVersion.V1, - PublicKey.fromBase58String("7E9Za8v8aT6EyU1sX91CVK7tWUeAetnNYDxzKZsyjyKV").explicitGet(), - PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), - AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, - OrderType.SELL, - 3, - 5000000000L, - 1526992336241L, - 1529584336241L, - 2, - proofs = Proofs(ByteStr.decodeBase58("2R6JfmNjEnbXAA6nt8YuCzSf1effDS4Wkz8owpCD9BdCNn864SnambTuwgLRYzzeP5CAsKHEviYKAJ2157vdr5Zq").get) - ) + val sell = Order(TxVersion.V1, PublicKey.fromBase58String("7E9Za8v8aT6EyU1sX91CVK7tWUeAetnNYDxzKZsyjyKV").explicitGet(), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.SELL, 3, 5000000000L, 1526992336241L, 1529584336241L, 2, proofs = Proofs(ByteStr.decodeBase58("2R6JfmNjEnbXAA6nt8YuCzSf1effDS4Wkz8owpCD9BdCNn864SnambTuwgLRYzzeP5CAsKHEviYKAJ2157vdr5Zq").get)) val proofs = Proofs(Seq(ByteStr.decodeBase58("5NxNhjMrrH5EWjSFnVnPbanpThic6fnNL48APVAkwq19y2FpQp4tNSqoAZgboC2ykUfqQs9suwBQj6wERmsWWNqa").get)) } diff --git a/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/GRPCBroadcastSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/GRPCBroadcastSpec.scala index abb85e579c4..b72c4e69362 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/GRPCBroadcastSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/GRPCBroadcastSpec.scala @@ -6,7 +6,7 @@ import scala.concurrent.duration.Duration import com.wavesplatform.account.Address import com.wavesplatform.common.state.ByteStr import com.wavesplatform.test.{FlatSpec, TestTime} -import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} +import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.BlockchainStubHelpers import com.wavesplatform.api.common.{CommonTransactionsApi, TransactionMeta} import com.wavesplatform.api.grpc.TransactionsApiGrpcImpl @@ -16,7 +16,6 @@ import com.wavesplatform.lang.ValidationError import com.wavesplatform.protobuf.transaction.PBTransactions import com.wavesplatform.state.{Blockchain, Height} import com.wavesplatform.transaction.{Asset, CreateAliasTransaction, Transaction, TxHelpers, TxVersion} -import com.wavesplatform.transaction.assets.exchange.{AssetPair, Order, OrderType} import com.wavesplatform.transaction.smart.script.trace.TracedResult import com.wavesplatform.transaction.TransactionType.TransactionType import com.wavesplatform.transaction.utils.EthTxGenerator @@ -39,39 +38,7 @@ class GRPCBroadcastSpec val FakeTime: TestTime = TestTime(100) "GRPC broadcast" should "accept Exchange with ETH orders" in { - val ethBuyOrder = Order( - Order.V4, - TestEthOrdersPublicKey, - TxHelpers.matcher.publicKey, - AssetPair(IssuedAsset(ByteStr(EthStubBytes32)), Waves), - OrderType.BUY, - 1, - 100L, - 1, - 123, - 100000, - Waves, - eip712Signature = EthSignature( - "0xe5ff562bfb0296e95b631365599c87f1c5002597bf56a131f289765275d2580f5344c62999404c37cd858ea037328ac91eca16ad1ce69c345ebb52fde70b66251c" - ) - ) - - val ethSellOrder = Order( - Order.V4, - TestEthOrdersPublicKey, - TxHelpers.matcher.publicKey, - AssetPair(IssuedAsset(ByteStr(EthStubBytes32)), Waves), - OrderType.SELL, - 1, - 100L, - 1, - 123, - 100000, - Waves, - eip712Signature = EthSignature( - "0xc8ba2bdafd27742546b3be34883efc51d6cdffbb235798d7b51876c6854791f019b0522d7a39b6f2087cba46ae86919b71a2d9d7920dfc8e00246d8f02a258f21b" - ) - ) + import com.wavesplatform.transaction.assets.exchange.EthOrderSpec.{ethBuyOrder, ethSellOrder} val blockchain = createBlockchainStub { blockchain => val sh = StubHelpers(blockchain) 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 6d2ee046600..b7d532162bd 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala @@ -73,135 +73,129 @@ class BlockchainUpdatesSpec extends FreeSpec with WithDomain with ScalaFutures w } "BlockchainUpdates" - { - "should return order ids in exchange metadata" in withSettings(DomainPresets.RideV4)(withDomainAndRepo { - case (d, repo) => - val issue = TxHelpers.issue() - d.appendBlock(TxHelpers.genesis(TxHelpers.defaultAddress)) - d.appendBlock(issue) + "should return order ids in exchange metadata" in withSettings(DomainPresets.RideV4)(withDomainAndRepo { case (d, repo) => + val issue = TxHelpers.issue() + d.appendBlock(TxHelpers.genesis(TxHelpers.defaultAddress)) + d.appendBlock(issue) - val subscription = repo.createFakeObserver(SubscribeRequest.of(1, 0)) - val exchange = TxHelpers.exchange(TxHelpers.order(OrderType.BUY, issue.asset), TxHelpers.order(OrderType.SELL, issue.asset)) - d.appendBlock(exchange) + val subscription = repo.createFakeObserver(SubscribeRequest.of(1, 0)) + val exchange = TxHelpers.exchange(TxHelpers.order(OrderType.BUY, issue.asset), TxHelpers.order(OrderType.SELL, issue.asset)) + d.appendBlock(exchange) - subscription.lastAppendEvent(d.blockchain).transactionMetadata should matchPattern { - case Seq(TransactionMetadata(TransactionMetadata.Metadata.Exchange(TransactionMetadata.ExchangeMetadata(ids, _)), _)) - if ids.map(_.toByteStr) == Seq(exchange.order1.id(), exchange.order2.id()) => - } + subscription.lastAppendEvent(d.blockchain).transactionMetadata should matchPattern { + case Seq(TransactionMetadata(TransactionMetadata.Metadata.Exchange(TransactionMetadata.ExchangeMetadata(ids, _)), _)) + if ids.map(_.toByteStr) == Seq(exchange.order1.id(), exchange.order2.id()) => + } }) - "should process nested invoke with args" in withDomainAndRepo { - case (d, repo) => - val script = TxHelpers.script(""" - |{-# STDLIB_VERSION 5 #-} - |{-# CONTENT_TYPE DAPP #-} - | - |@Callable(inv) - |func foo() = { - | strict ii = invoke(this, "bar", [1], []) - | [IntegerEntry("test1", 1)] - |} - | - |@Callable(inv) - |func bar(i: Int) = [IntegerEntry("test", 2)] - | + "should process nested invoke with args" in withDomainAndRepo { case (d, repo) => + val script = TxHelpers.script(""" + |{-# STDLIB_VERSION 5 #-} + |{-# CONTENT_TYPE DAPP #-} + | + |@Callable(inv) + |func foo() = { + | strict ii = invoke(this, "bar", [1], []) + | [IntegerEntry("test1", 1)] + |} + | + |@Callable(inv) + |func bar(i: Int) = [IntegerEntry("test", 2)] + | """.stripMargin) - d.appendBlock(TxHelpers.genesis(TxHelpers.defaultAddress)) - d.appendBlock(TxHelpers.setScript(TxHelpers.defaultSigner, script)) - d.appendBlock(TxHelpers.invoke(TxHelpers.defaultAddress, "foo")) - - val subscription = repo.createFakeObserver(SubscribeRequest.of(1, 0)) - val events = subscription.fetchAllEvents(d.blockchain).map(_.getUpdate.vanillaAppend) - val invocations = events.last.transactionMetadata.head.getInvokeScript.getResult.invokes + d.appendBlock(TxHelpers.genesis(TxHelpers.defaultAddress)) + d.appendBlock(TxHelpers.setScript(TxHelpers.defaultSigner, script)) + d.appendBlock(TxHelpers.invoke(TxHelpers.defaultAddress, "foo")) - invocations shouldBe List( - Invocation( - ByteString.copyFrom(TxHelpers.defaultAddress.bytes), - Some(Call("bar", args = Seq(Call.Argument(Call.Argument.Value.IntegerValue(1))))), - stateChanges = Some(InvokeScriptResult(data = Seq(DataEntry("test", DataEntry.Value.IntValue(2))))) - ) + val subscription = repo.createFakeObserver(SubscribeRequest.of(1, 0)) + val events = subscription.fetchAllEvents(d.blockchain).map(_.getUpdate.vanillaAppend) + val invocations = events.last.transactionMetadata.head.getInvokeScript.getResult.invokes + + invocations shouldBe List( + Invocation( + ByteString.copyFrom(TxHelpers.defaultAddress.bytes), + Some(Call("bar", args = Seq(Call.Argument(Call.Argument.Value.IntegerValue(1))))), + stateChanges = Some(InvokeScriptResult(data = Seq(DataEntry("test", DataEntry.Value.IntValue(2))))) ) + ) } - "should not freeze on micro rollback" in withDomainAndRepo { - case (d, repo) => - val keyBlockId = d.appendKeyBlock().id() - d.appendMicroBlock(TxHelpers.transfer()) - d.appendMicroBlock(TxHelpers.transfer()) + "should not freeze on micro rollback" in withDomainAndRepo { case (d, repo) => + val keyBlockId = d.appendKeyBlock().id() + d.appendMicroBlock(TxHelpers.transfer()) + d.appendMicroBlock(TxHelpers.transfer()) - val subscription = repo.createFakeObserver(SubscribeRequest.of(1, 0)) - d.appendKeyBlock(Some(keyBlockId)) + val subscription = repo.createFakeObserver(SubscribeRequest.of(1, 0)) + d.appendKeyBlock(Some(keyBlockId)) - subscription.fetchAllEvents(d.blockchain).map(_.getUpdate) should matchPattern { - case Seq( + subscription.fetchAllEvents(d.blockchain).map(_.getUpdate) should matchPattern { + case Seq( E.Block(1, _), E.Micro(1, _), E.Micro(1, _), E.MicroRollback(1, `keyBlockId`), E.Block(2, _) - ) => - } + ) => + } } - "should not freeze on block rollback" in withDomainAndRepo { - case (d, repo) => - val block1Id = d.appendKeyBlock().id() - d.appendKeyBlock() + "should not freeze on block rollback" in withDomainAndRepo { case (d, repo) => + val block1Id = d.appendKeyBlock().id() + d.appendKeyBlock() - val subscription = repo.createFakeObserver(SubscribeRequest.of(1, 0)) - d.rollbackTo(block1Id) - d.appendKeyBlock() + val subscription = repo.createFakeObserver(SubscribeRequest.of(1, 0)) + d.rollbackTo(block1Id) + d.appendKeyBlock() - subscription.fetchAllEvents(d.blockchain).map(_.getUpdate) should matchPattern { - case Seq( + subscription.fetchAllEvents(d.blockchain).map(_.getUpdate) should matchPattern { + case Seq( E.Block(1, _), E.Block(2, _), E.Rollback(1, `block1Id`), E.Block(2, _) - ) => - } + ) => + } } - "should not duplicate blocks" in withDomainAndRepo { - case (d, repo) => - for (_ <- 1 to 99) d.appendBlock() - d.appendKeyBlock() - d.appendMicroBlock(TxHelpers.transfer()) - d.appendKeyBlock() + "should not duplicate blocks" in withDomainAndRepo { case (d, repo) => + for (_ <- 1 to 99) d.appendBlock() + d.appendKeyBlock() + d.appendMicroBlock(TxHelpers.transfer()) + d.appendKeyBlock() - val events = { - val sub = repo.createFakeObserver(SubscribeRequest.of(1, 0)) - sub.fetchAllEvents(d.blockchain).map(_.getUpdate) - } + val events = { + val sub = repo.createFakeObserver(SubscribeRequest.of(1, 0)) + sub.fetchAllEvents(d.blockchain).map(_.getUpdate) + } - val lastEvents = events.dropWhile(_.height < 100) - lastEvents should matchPattern { - case Seq( + val lastEvents = events.dropWhile(_.height < 100) + lastEvents should matchPattern { + case Seq( E.Block(100, _), E.Block(101, _) - ) => - } + ) => + } } - "should not freeze on block rollback without key-block" in withDomainAndRepo { - case (d, repo) => - val block1Id = d.appendBlock().id() - val block2Id = d.appendBlock().id() - d.appendBlock() - d.rollbackTo(block2Id) + "should not freeze on block rollback without key-block" in withDomainAndRepo { case (d, repo) => + val block1Id = d.appendBlock().id() + val block2Id = d.appendBlock().id() + d.appendBlock() + d.rollbackTo(block2Id) - val subscription = repo.createFakeObserver(SubscribeRequest.of(1, 0)) - d.rollbackTo(block1Id) - d.appendBlock() + val subscription = repo.createFakeObserver(SubscribeRequest.of(1, 0)) + d.rollbackTo(block1Id) + d.appendBlock() - subscription.fetchAllEvents(d.blockchain).map(_.getUpdate) should matchPattern { - case Seq( + subscription.fetchAllEvents(d.blockchain).map(_.getUpdate) should matchPattern { + case Seq( E.Block(1, _), E.Block(2, _), E.Rollback(1, `block1Id`), E.Block(2, _) - ) => - } + ) => + } } "should survive invalid rollback" in withDomain( @@ -216,27 +210,26 @@ class BlockchainUpdatesSpec extends FreeSpec with WithDomain with ScalaFutures w } } - "should survive invalid micro rollback" in withDomainAndRepo { - case (d, repo) => - d.appendKeyBlock() - val sub = repo.createFakeObserver(SubscribeRequest(1)) - val mb1Id = d.appendMicroBlock(TxHelpers.transfer()) - val mb2Id = d.appendMicroBlock(TxHelpers.transfer()) - d.appendMicroBlock(TxHelpers.transfer()) + "should survive invalid micro rollback" in withDomainAndRepo { case (d, repo) => + d.appendKeyBlock() + val sub = repo.createFakeObserver(SubscribeRequest(1)) + val mb1Id = d.appendMicroBlock(TxHelpers.transfer()) + val mb2Id = d.appendMicroBlock(TxHelpers.transfer()) + d.appendMicroBlock(TxHelpers.transfer()) - d.blockchain.removeAfter(mb1Id) // Should not do anything - d.appendKeyBlock(ref = Some(mb2Id)) + d.blockchain.removeAfter(mb1Id) // Should not do anything + d.appendKeyBlock(ref = Some(mb2Id)) - sub.fetchAllEvents(d.blockchain).map(_.getUpdate) should matchPattern { - case Seq( + sub.fetchAllEvents(d.blockchain).map(_.getUpdate) should matchPattern { + case Seq( E.Block(1, _), E.Micro(1, _), E.Micro(1, _), E.Micro(1, _), E.MicroRollback(1, `mb2Id`), E.Block(2, _) - ) => - } + ) => + } } "should survive rollback to key block" in withDomainAndRepo { (d, repo) => @@ -248,11 +241,11 @@ class BlockchainUpdatesSpec extends FreeSpec with WithDomain with ScalaFutures w subscription.fetchAllEvents(d.blockchain).map(_.getUpdate) should matchPattern { case Seq( - E.Block(1, _), - E.Block(2, _), - E.Micro(2, _), - E.MicroRollback(2, `keyBlockId`), - E.Block(3, _) + E.Block(1, _), + E.Block(2, _), + E.Micro(2, _), + E.MicroRollback(2, `keyBlockId`), + E.Block(3, _) ) => } } @@ -462,22 +455,22 @@ class BlockchainUpdatesSpec extends FreeSpec with WithDomain with ScalaFutures w val (dAppScript, _) = ScriptCompiler .compile( s""" - |{-# STDLIB_VERSION 5 #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - |{-# CONTENT_TYPE DAPP #-} - | - |@Callable(i) - |func issue() = { - | let issue = Issue("name", "description", 1000, 4, true, unit, 0) - | let lease = Lease(i.caller, 500000000) - | [ - | issue, - | BinaryEntry("assetId", calculateAssetId(issue)), - | lease, - | BinaryEntry("leaseId", calculateLeaseId(lease)) - | ] - |} - |""".stripMargin, + |{-# STDLIB_VERSION 5 #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + |{-# CONTENT_TYPE DAPP #-} + | + |@Callable(i) + |func issue() = { + | let issue = Issue("name", "description", 1000, 4, true, unit, 0) + | let lease = Lease(i.caller, 500000000) + | [ + | issue, + | BinaryEntry("assetId", calculateAssetId(issue)), + | lease, + | BinaryEntry("leaseId", calculateLeaseId(lease)) + | ] + |} + |""".stripMargin, ScriptEstimatorV3(fixOverflow = true, overhead = false) ) .explicitGet() @@ -535,7 +528,7 @@ class BlockchainUpdatesSpec extends FreeSpec with WithDomain with ScalaFutures w val subscription = repo.createFakeObserver(request) generateBlocks(d) - val result = subscription.fetchAllEvents(d.blockchain, request.toHeight) + val result = subscription.fetchAllEvents(d.blockchain, if (request.toHeight > 0) request.toHeight else Int.MaxValue) f(result.map(_.getUpdate)) } } diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/activation/AcceptFailedScriptActivationSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/activation/AcceptFailedScriptActivationSuite.scala index f2c3f229f4a..7183059c588 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/activation/AcceptFailedScriptActivationSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/activation/AcceptFailedScriptActivationSuite.scala @@ -1,19 +1,21 @@ package com.wavesplatform.it.sync.activation +import scala.concurrent.duration.* + import com.typesafe.config.Config import com.wavesplatform.api.http.ApiError.StateCheckFailed import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.features.BlockchainFeatures +import com.wavesplatform.it.{NodeConfigs, NTPTime} import com.wavesplatform.it.NodeConfigs.Default -import com.wavesplatform.it.api.SyncHttpApi._ +import com.wavesplatform.it.api.SyncHttpApi.* import com.wavesplatform.it.api.TransactionStatus -import com.wavesplatform.it.sync._ +import com.wavesplatform.it.sync.* import com.wavesplatform.it.sync.transactions.OverflowBlock import com.wavesplatform.it.transactions.BaseTransactionSuite -import com.wavesplatform.it.{NTPTime, NodeConfigs} import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 -import com.wavesplatform.test._ +import com.wavesplatform.test.* import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.TxVersion import com.wavesplatform.transaction.assets.exchange.{AssetPair, Order} @@ -21,10 +23,8 @@ import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.smart.script.ScriptCompiler import play.api.libs.json.JsObject -import scala.concurrent.duration._ - class AcceptFailedScriptActivationSuite extends BaseTransactionSuite with NTPTime with OverflowBlock { - import AcceptFailedScriptActivationSuite._ + import AcceptFailedScriptActivationSuite.* private lazy val (dApp, dAppKP) = (firstAddress, firstKeyPair) private lazy val (caller, callerKP) = (secondAddress, secondKeyPair) diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/ExchangeTransactionSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/ExchangeTransactionSuite.scala index f963595d5df..93cc083c945 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/ExchangeTransactionSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/ExchangeTransactionSuite.scala @@ -4,18 +4,18 @@ import com.typesafe.config.Config import com.wavesplatform.api.http.ApiError.{CustomValidationError, StateCheckFailed} import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.features.BlockchainFeatures -import com.wavesplatform.it.api.SyncHttpApi._ -import com.wavesplatform.it.sync._ +import com.wavesplatform.it.{NodeConfigs, NTPTime} +import com.wavesplatform.it.api.SyncHttpApi.* +import com.wavesplatform.it.sync.* import com.wavesplatform.it.sync.smartcontract.exchangeTx import com.wavesplatform.it.transactions.BaseTransactionSuite -import com.wavesplatform.it.{NTPTime, NodeConfigs} -import com.wavesplatform.test._ +import com.wavesplatform.test.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxVersion import com.wavesplatform.transaction.assets.IssueTransaction -import com.wavesplatform.transaction.assets.exchange._ -import com.wavesplatform.utils._ -import play.api.libs.json.{JsNumber, JsObject, JsString, Json} +import com.wavesplatform.transaction.assets.exchange.* +import com.wavesplatform.utils.* +import play.api.libs.json.{JsNumber, JsObject, Json, JsString} class ExchangeTransactionSuite extends BaseTransactionSuite with NTPTime { private lazy val exchAsset: IssueTransaction = IssueTransaction( @@ -99,10 +99,12 @@ class ExchangeTransactionSuite extends BaseTransactionSuite with NTPTime { if (sender.findTransactionInfo(exchAsset.id().toString).isEmpty) sender.postJson("/transactions/broadcast", exchAsset.json()) val pair = AssetPair.createAssetPair("WAVES", exchAsset.id().toString).get - for ((o1ver, o2ver) <- Seq( - (2: Byte, 1: Byte), - (2: Byte, 3: Byte) - )) { + for ( + (o1ver, o2ver) <- Seq( + (2: Byte, 1: Byte), + (2: Byte, 3: Byte) + ) + ) { val tx = exchangeTx(pair, matcherFee, orderFee, ntpTime, o1ver, o2ver, acc1, acc0, acc2) val sig = (Json.parse(tx.toString()) \ "proofs").as[Seq[JsString]].head val changedTx = tx + ("version" -> JsNumber(1)) + ("signature" -> sig) @@ -137,14 +139,16 @@ class ExchangeTransactionSuite extends BaseTransactionSuite with NTPTime { sender.transfer(firstKeyPair, secondKeyPair.toAddress.toString, IssueTx.quantity / 2, assetId = Some(assetId.toString), waitForTx = true) - for ((o1ver, o2ver, matcherFeeOrder1, matcherFeeOrder2) <- Seq( - (1: Byte, 3: Byte, Waves, IssuedAsset(assetId)), - (1: Byte, 3: Byte, Waves, Waves), - (2: Byte, 3: Byte, Waves, IssuedAsset(assetId)), - (3: Byte, 1: Byte, IssuedAsset(assetId), Waves), - (2: Byte, 3: Byte, Waves, Waves), - (3: Byte, 2: Byte, IssuedAsset(assetId), Waves) - )) { + for ( + (o1ver, o2ver, matcherFeeOrder1, matcherFeeOrder2) <- Seq( + (1: Byte, 3: Byte, Waves, IssuedAsset(assetId)), + (1: Byte, 3: Byte, Waves, Waves), + (2: Byte, 3: Byte, Waves, IssuedAsset(assetId)), + (3: Byte, 1: Byte, IssuedAsset(assetId), Waves), + (2: Byte, 3: Byte, Waves, Waves), + (3: Byte, 2: Byte, IssuedAsset(assetId), Waves) + ) + ) { val matcher = thirdKeyPair val ts = ntpTime.correctedTime() @@ -161,9 +165,9 @@ class ExchangeTransactionSuite extends BaseTransactionSuite with NTPTime { val buyAmount = 40000000 val sellAmount = 40000000 val assetPair = AssetPair.createAssetPair("WAVES", assetId.toString).get - val buy = Order.buy(o1ver, buyer, matcher.publicKey, assetPair, buyAmount, buyPrice, ts, expirationTimestamp, matcherFee, matcherFeeOrder1) - val sell = Order.sell(o2ver, seller, matcher.publicKey, assetPair, sellAmount, sellPrice, ts, expirationTimestamp, matcherFee, matcherFeeOrder2) - val amount = 40000000 + val buy = Order.buy(o1ver, buyer, matcher.publicKey, assetPair, buyAmount, buyPrice, ts, expirationTimestamp, matcherFee, matcherFeeOrder1) + val sell = Order.sell(o2ver, seller, matcher.publicKey, assetPair, sellAmount, sellPrice, ts, expirationTimestamp, matcherFee, matcherFeeOrder2) + val amount = 40000000 val tx = ExchangeTransaction @@ -239,14 +243,38 @@ class ExchangeTransactionSuite extends BaseTransactionSuite with NTPTime { val nftOtherAssetPair = AssetPair.createAssetPair(nftAsset, dec6AssetId).get val sellNftForWaves = - Order.sell(4.toByte, seller, matcher.publicKey, nftWavesPair, amount, nftWavesPrice, ts, expirationTimestamp, matcherFee, Waves) + Order.sell(4.toByte, seller, matcher.publicKey, nftWavesPair, amount, nftWavesPrice, ts, expirationTimestamp, matcherFee, Waves, OrderPriceMode.Default) val buyNftForWaves = - Order.buy(4.toByte, buyer, matcher.publicKey, nftWavesPair, amount, nftWavesPrice, ts, expirationTimestamp, matcherFee, Waves) + Order.buy(4.toByte, buyer, matcher.publicKey, nftWavesPair, amount, nftWavesPrice, ts, expirationTimestamp, matcherFee, Waves, OrderPriceMode.Default) val sellNftForOtherAsset = - Order.sell(4.toByte, buyer, matcher.publicKey, nftOtherAssetPair, amount, nftForAssetPrice, ts, expirationTimestamp, matcherFee, Waves) + Order.sell( + 4.toByte, + buyer, + matcher.publicKey, + nftOtherAssetPair, + amount, + nftForAssetPrice, + ts, + expirationTimestamp, + matcherFee, + Waves, + OrderPriceMode.Default + ) val buyNftForOtherAsset = - Order.buy(4.toByte, seller, matcher.publicKey, nftOtherAssetPair, amount, nftForAssetPrice, ts, expirationTimestamp, matcherFee, Waves) + Order.buy( + 4.toByte, + seller, + matcher.publicKey, + nftOtherAssetPair, + amount, + nftForAssetPrice, + ts, + expirationTimestamp, + matcherFee, + Waves, + OrderPriceMode.Default + ) val sellerAddress = sellerKeyPair.toAddress.toString val sellerBalance = sender.balanceDetails(sellerAddress).regular diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/FailedTransactionSuiteLike.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/FailedTransactionSuiteLike.scala index ec9de07aced..3d0ca11f4cb 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/FailedTransactionSuiteLike.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/FailedTransactionSuiteLike.scala @@ -1,34 +1,36 @@ package com.wavesplatform.it.sync.transactions +import scala.concurrent.duration.* + import com.google.protobuf.ByteString import com.typesafe.config.{Config, ConfigFactory} import com.wavesplatform.account.KeyPair -import com.wavesplatform.api.grpc.{ApplicationStatus, TransactionsByIdRequest, TransactionStatus => PBTransactionStatus} +import com.wavesplatform.api.grpc.{ApplicationStatus, TransactionsByIdRequest, TransactionStatus as PBTransactionStatus} import com.wavesplatform.api.http.ApiError.TransactionDoesNotExist import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.it.api.TransactionStatus import com.wavesplatform.it.{Node, NodeConfigs} +import com.wavesplatform.it.api.TransactionStatus import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 import com.wavesplatform.protobuf.transaction.{PBSignedTransaction, PBTransactions} +import com.wavesplatform.transaction.{Asset, TxVersion} import com.wavesplatform.transaction.assets.exchange.{AssetPair, ExchangeTransaction, Order} import com.wavesplatform.transaction.smart.script.ScriptCompiler -import com.wavesplatform.transaction.{Asset, TxVersion} import com.wavesplatform.utils.ScorexLogging import org.scalatest.matchers.should.Matchers import play.api.libs.json.JsObject -import scala.concurrent.duration._ - trait FailedTransactionSuiteLike[T] extends ScorexLogging { _: Matchers => protected def waitForHeightArise(): Unit protected def sender: Node - /** - * Sends `max-transactions-in-micro-block` * 2 transactions and then sends priority transaction. - * @param t transaction sender - * @param pt priority transaction sender - * @param checker transactions checker (will be executed twice - immediately after emptying the utx pool and then after height arising) + /** Sends `max-transactions-in-micro-block` * 2 transactions and then sends priority transaction. + * @param t + * transaction sender + * @param pt + * priority transaction sender + * @param checker + * transactions checker (will be executed twice - immediately after emptying the utx pool and then after height arising) */ def sendTxsAndThenPriorityTx[S](t: Int => T, pt: () => T)( checker: (Seq[T], T) => Seq[S] @@ -43,10 +45,9 @@ trait FailedTransactionSuiteLike[T] extends ScorexLogging { _: Matchers => } object restApi { - import com.wavesplatform.it.api.SyncHttpApi.{NodeExtSync, assertApiError} + import com.wavesplatform.it.api.SyncHttpApi.{assertApiError, NodeExtSync} - /** - * Checks that transactions contain failed and returns them. + /** Checks that transactions contain failed and returns them. */ def assertFailedTxs(txs: Seq[String]): Seq[TransactionStatus] = { val statuses = sender.transactionStatus(txs).sortWith { case (f, s) => txs.indexOf(f.id) < txs.indexOf(s.id) } @@ -64,24 +65,22 @@ trait FailedTransactionSuiteLike[T] extends ScorexLogging { _: Matchers => val failedIdsByHeight = failed.groupBy(_.height.get).view.mapValues(_.map(_.id)) - failedIdsByHeight.foreach { - case (h, ids) => - sender.blockAt(h).transactions.map(_.id) should contain allElementsOf ids - sender.blockSeq(h, h).head.transactions.map(_.id) should contain allElementsOf ids - sender.blockById(sender.blockAt(h).id).transactions.map(_.id) should contain allElementsOf ids - sender.blockSeqByAddress(sender.address, h, h).head.transactions.map(_.id) should contain allElementsOf ids - - val liquidBlock = sender.lastBlock() - val maxHeightWithFailed = failedIdsByHeight.keys.max - if (liquidBlock.height == maxHeightWithFailed) { - liquidBlock.transactions.map(_.id) should contain allElementsOf failedIdsByHeight(maxHeightWithFailed) - } + failedIdsByHeight.foreach { case (h, ids) => + sender.blockAt(h).transactions.map(_.id) should contain allElementsOf ids + sender.blockSeq(h, h).head.transactions.map(_.id) should contain allElementsOf ids + sender.blockById(sender.blockAt(h).id).transactions.map(_.id) should contain allElementsOf ids + sender.blockSeqByAddress(sender.address, h, h).head.transactions.map(_.id) should contain allElementsOf ids + + val liquidBlock = sender.lastBlock() + val maxHeightWithFailed = failedIdsByHeight.keys.max + if (liquidBlock.height == maxHeightWithFailed) { + liquidBlock.transactions.map(_.id) should contain allElementsOf failedIdsByHeight(maxHeightWithFailed) + } } failed } - /** - * Checks that transactions contain invalid and returns them. + /** Checks that transactions contain invalid and returns them. */ def assertInvalidTxs(txs: Seq[String]): Seq[TransactionStatus] = { val statuses = sender.transactionStatus(txs).sortWith { case (f, s) => txs.indexOf(f.id) < txs.indexOf(s.id) } @@ -163,10 +162,9 @@ trait FailedTransactionSuiteLike[T] extends ScorexLogging { _: Matchers => } object grpcApi { - import com.wavesplatform.it.api.SyncGrpcApi._ + import com.wavesplatform.it.api.SyncGrpcApi.* - /** - * Checks that transactions contain failed and returns them. + /** Checks that transactions contain failed and returns them. */ def assertFailedTxs(txs: Seq[PBSignedTransaction]): Seq[PBTransactionStatus] = { val txsIds = txs.map(PBTransactions.vanillaUnsafe).map(tx => ByteString.copyFrom(tx.id().arr)) @@ -183,17 +181,15 @@ trait FailedTransactionSuiteLike[T] extends ScorexLogging { _: Matchers => val failedIdsByHeight = failed.groupBy(_.height.toInt).view.mapValues(_.map(_.id)) - failedIdsByHeight.foreach { - case (h, ids) => - sender.blockAt(h).transactionData.map(_.id()) should contain allElementsOf ids.map(bs => ByteStr(bs.toByteArray)) - sender.blockSeq(h, h).head.transactionData.map(_.id()) should contain allElementsOf ids.map(bs => ByteStr(bs.toByteArray)) + failedIdsByHeight.foreach { case (h, ids) => + sender.blockAt(h).transactionData.map(_.id()) should contain allElementsOf ids.map(bs => ByteStr(bs.toByteArray)) + sender.blockSeq(h, h).head.transactionData.map(_.id()) should contain allElementsOf ids.map(bs => ByteStr(bs.toByteArray)) } failed } - /** - * Checks that transactions contain invalid and returns them. + /** Checks that transactions contain invalid and returns them. */ def assertInvalidTxs(txs: Seq[PBSignedTransaction]): Seq[PBTransactionStatus] = { val txsIds = txs.map(PBTransactions.vanillaUnsafe).map(tx => ByteString.copyFrom(tx.id().arr)) @@ -217,13 +213,13 @@ trait FailedTransactionSuiteLike[T] extends ScorexLogging { _: Matchers => ScriptCompiler .compile( s""" - |match tx { - | case _: SetAssetScriptTransaction => true - | case _ => - | let check = ${"sigVerify(base58'', base58'', base58'') ||" * 16} false - | if (check) then false else $result - |} - |""".stripMargin, + |match tx { + | case _: SetAssetScriptTransaction => true + | case _ => + | let check = ${"sigVerify(base58'', base58'', base58'') ||" * 16} false + | if (check) then false else $result + |} + |""".stripMargin, ScriptEstimatorV3(fixOverflow = true, overhead = false) ) .explicitGet() @@ -244,15 +240,15 @@ trait FailedTransactionSuiteLike[T] extends ScorexLogging { _: Matchers => ScriptCompiler .compile( s""" - |{-# STDLIB_VERSION 3 #-} - |{-# CONTENT_TYPE EXPRESSION #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - | - |match (tx) { - | case _: SetScriptTransaction => true - | case _ => $r - |} - |""".stripMargin, + |{-# STDLIB_VERSION 3 #-} + |{-# CONTENT_TYPE EXPRESSION #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |match (tx) { + | case _: SetScriptTransaction => true + | case _ => $r + |} + |""".stripMargin, ScriptEstimatorV3(fixOverflow = true, overhead = false) ) .toOption @@ -265,7 +261,7 @@ trait FailedTransactionSuiteLike[T] extends ScorexLogging { _: Matchers => } def waitForEmptyUtx(): Unit = { - import com.wavesplatform.it.api.SyncHttpApi._ + import com.wavesplatform.it.api.SyncHttpApi.* sender.waitFor("empty utx")(n => n.utxSize, (utxSize: Int) => utxSize == 0, 100.millis) } @@ -283,11 +279,31 @@ object FailedTransactionSuiteLike { buyMatcherFee: Long, sellMatcherFee: Long ): ExchangeTransaction = { - val ts = System.currentTimeMillis() - val bmfa = Asset.fromString(Some(buyMatcherFeeAsset)) - val smfa = Asset.fromString(Some(sellMatcherFeeAsset)) - val buy = Order.buy(Order.V4, buyer, matcher.publicKey, assetPair, 100, 100, ts, ts + Order.MaxLiveTime, buyMatcherFee, bmfa) - val sell = Order.sell(Order.V4, seller, matcher.publicKey, assetPair, 100, 100, ts, ts + Order.MaxLiveTime, sellMatcherFee, smfa) + val timestamp = System.currentTimeMillis() + val buy = Order.buy( + Order.V4, + buyer, + matcher.publicKey, + assetPair, + 100, + 100, + timestamp, + timestamp + Order.MaxLiveTime, + buyMatcherFee, + Asset.fromString(Some(buyMatcherFeeAsset)) + ) + val sell = Order.sell( + Order.V4, + seller, + matcher.publicKey, + assetPair, + 100, + 100, + timestamp, + timestamp + Order.MaxLiveTime, + sellMatcherFee, + Asset.fromString(Some(sellMatcherFeeAsset)) + ) ExchangeTransaction .signed( TxVersion.V3, @@ -299,15 +315,15 @@ object FailedTransactionSuiteLike { buy.matcherFee, sell.matcherFee, fee, - ts + timestamp ) .explicitGet() } val configForMinMicroblockAge: Config = ConfigFactory.parseString(s""" - |waves.miner.min-micro-block-age = 7 - |waves.miner.max-transactions-in-micro-block = 1 - |""".stripMargin) + |waves.miner.min-micro-block-age = 7 + |waves.miner.max-transactions-in-micro-block = 1 + |""".stripMargin) val Configs: Seq[Config] = NodeConfigs.newBuilder diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/utils/TransactionSerializeSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/utils/TransactionSerializeSuite.scala index d120cd06050..a4be73713cd 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/utils/TransactionSerializeSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/utils/TransactionSerializeSuite.scala @@ -30,47 +30,11 @@ class TransactionSerializeSuite extends BaseTransactionSuite with TableDrivenPro private val tsOrderFrom: Long = 1526992336241L private val tsOrderTo: Long = 1529584336241L - private lazy val buyV2 = Order( - TxVersion.V2, - PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet(), - PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), - AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, - OrderType.BUY, - 2, - 60.waves, - tsOrderFrom, - tsOrderTo, - 1, - proofs = Proofs(Seq(ByteStr.decodeBase58("2bkuGwECMFGyFqgoHV4q7GRRWBqYmBFWpYRkzgYANR4nN2twgrNaouRiZBqiK2RJzuo9NooB9iRiuZ4hypBbUQs").get)) - ) + private lazy val buyV2 = Order(TxVersion.V2, OrderAuthentication(PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet()), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.BUY, 2, 60.waves, tsOrderFrom, tsOrderTo, 1) - private lazy val buyV1 = Order( - TxVersion.V1, - PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet(), - PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), - AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, - OrderType.BUY, - 2, - 60.waves, - tsOrderFrom, - tsOrderTo, - 1, - proofs = Proofs(ByteStr.decodeBase58("2bkuGwECMFGyFqgoHV4q7GRRWBqYmBFWpYRkzgYANR4nN2twgrNaouRiZBqiK2RJzuo9NooB9iRiuZ4hypBbUQs").get) - ) + private lazy val buyV1 = Order(TxVersion.V1, OrderAuthentication(PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet()), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.BUY, 2, 60.waves, tsOrderFrom, tsOrderTo, 1) - private lazy val sell = Order( - TxVersion.V1, - PublicKey.fromBase58String("7E9Za8v8aT6EyU1sX91CVK7tWUeAetnNYDxzKZsyjyKV").explicitGet(), - PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), - AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, - OrderType.SELL, - 3, - 50.waves, - tsOrderFrom, - tsOrderTo, - 2, - proofs = Proofs(ByteStr.decodeBase58("2R6JfmNjEnbXAA6nt8YuCzSf1effDS4Wkz8owpCD9BdCNn864SnambTuwgLRYzzeP5CAsKHEviYKAJ2157vdr5Zq").get) - ) + private lazy val sell = Order(TxVersion.V1, OrderAuthentication(PublicKey.fromBase58String("7E9Za8v8aT6EyU1sX91CVK7tWUeAetnNYDxzKZsyjyKV").explicitGet()), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.SELL, 3, 50.waves, tsOrderFrom, tsOrderTo, 2) private lazy val exV1 = ExchangeTransaction .create( diff --git a/node/src/main/scala/com/wavesplatform/protobuf/transaction/PBOrders.scala b/node/src/main/scala/com/wavesplatform/protobuf/transaction/PBOrders.scala index 46ca203ebe3..b7adb563bcf 100644 --- a/node/src/main/scala/com/wavesplatform/protobuf/transaction/PBOrders.scala +++ b/node/src/main/scala/com/wavesplatform/protobuf/transaction/PBOrders.scala @@ -1,18 +1,25 @@ package com.wavesplatform.protobuf.transaction -import com.wavesplatform.{transaction => vt} +import com.wavesplatform.transaction as vt import com.wavesplatform.account.{AddressScheme, PublicKey} -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.protobuf._ +import com.wavesplatform.protobuf.* import com.wavesplatform.protobuf.order.AssetPair +import com.wavesplatform.protobuf.order.Order.{PriceMode, Sender} +import com.wavesplatform.protobuf.order.Order.PriceMode.{ASSET_DECIMALS, FIXED_DECIMALS, DEFAULT as DEFAULT_PRICE_MODE} +import com.wavesplatform.transaction.assets.exchange.OrderPriceMode.{AssetDecimals, FixedDecimals, Default as DefaultPriceMode} +import vt.assets.exchange.OrderAuthentication object PBOrders { - import com.wavesplatform.protobuf.utils.PBImplicitConversions._ + import com.wavesplatform.protobuf.utils.PBImplicitConversions.* - def vanilla(order: PBOrder, version: Int = 0): VanillaOrder = { + def vanilla(order: PBOrder): VanillaOrder = { VanillaOrder( - if (version == 0) order.version.toByte else version.toByte, - PublicKey(order.senderPublicKey.toByteArray), + order.version.toByte, + order.sender match { + case Sender.SenderPublicKey(value) => OrderAuthentication.OrderProofs(PublicKey(value.toByteStr), order.proofs.map(_.toByteStr)) + case Sender.Eip712Signature(sig) => OrderAuthentication.Eip712Signature(sig.toByteStr) + case Sender.Empty => throw new IllegalArgumentException("Order should have either senderPublicKey or eip712Signature") + }, PublicKey(order.matcherPublicKey.toByteArray), vt.assets.exchange .AssetPair(PBAmounts.toVanillaAssetId(order.getAssetPair.amountAssetId), PBAmounts.toVanillaAssetId(order.getAssetPair.priceAssetId)), @@ -27,15 +34,18 @@ object PBOrders { order.expiration, order.getMatcherFee.longAmount, PBAmounts.toVanillaAssetId(order.getMatcherFee.assetId), - order.proofs.map(_.toByteStr), - Some(order.eip712Signature.toByteStr).filterNot(_.isEmpty) + order.priceMode match { + case DEFAULT_PRICE_MODE => DefaultPriceMode + case ASSET_DECIMALS => AssetDecimals + case FIXED_DECIMALS => FixedDecimals + case PriceMode.Unrecognized(v) => throw new IllegalArgumentException(s"Unknown order price mode: $v") + } ) } def protobuf(order: VanillaOrder): PBOrder = { PBOrder( AddressScheme.current.chainId, - order.senderPublicKey.toByteString, order.matcherPublicKey.toByteString, Some(AssetPair(PBAmounts.toPBAssetId(order.assetPair.amountAsset), PBAmounts.toPBAssetId(order.assetPair.priceAsset))), order.orderType match { @@ -49,7 +59,15 @@ object PBOrders { Some((order.matcherFeeAssetId, order.matcherFee)), order.version, order.proofs.map(_.toByteString), - order.eip712Signature.getOrElse(ByteStr.empty).toByteString + order.priceMode match { + case DefaultPriceMode => DEFAULT_PRICE_MODE + case AssetDecimals => ASSET_DECIMALS + case FixedDecimals => FIXED_DECIMALS + }, + order.orderAuthentication match { + case OrderAuthentication.OrderProofs(key, _) => Sender.SenderPublicKey(key.toByteString) + case OrderAuthentication.Eip712Signature(signature) => Sender.Eip712Signature(signature.toByteString) + } ) } } diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/CommonValidation.scala b/node/src/main/scala/com/wavesplatform/state/diffs/CommonValidation.scala index 2a7879b53e7..09a2f543ecf 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/CommonValidation.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/CommonValidation.scala @@ -1,28 +1,28 @@ package com.wavesplatform.state.diffs -import cats._ +import scala.util.{Left, Right} + +import cats.Monoid import com.wavesplatform.account.{Address, AddressScheme} -import com.wavesplatform.features.OverdraftValidationProvider._ import com.wavesplatform.features.{BlockchainFeature, BlockchainFeatures, RideVersionProvider} +import com.wavesplatform.features.OverdraftValidationProvider._ import com.wavesplatform.lang.ValidationError import com.wavesplatform.lang.directives.values._ +import com.wavesplatform.lang.script.{ContractScript, Script} import com.wavesplatform.lang.script.ContractScript.ContractScriptImpl import com.wavesplatform.lang.script.v1.ExprScript -import com.wavesplatform.lang.script.{ContractScript, Script} import com.wavesplatform.settings.FunctionalitySettings import com.wavesplatform.state._ +import com.wavesplatform.transaction._ import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError._ -import com.wavesplatform.transaction._ import com.wavesplatform.transaction.assets._ import com.wavesplatform.transaction.assets.exchange._ import com.wavesplatform.transaction.lease._ -import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.smart.{InvokeExpressionTransaction, InvokeScriptTransaction, SetScriptTransaction} +import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.transfer._ -import scala.util.{Left, Right} - object CommonValidation { def disallowSendingGreaterThanBalance[T <: Transaction](blockchain: Blockchain, blockTime: Long, tx: T): Either[ValidationError, T] = if (blockTime >= blockchain.settings.functionalitySettings.allowTemporaryNegativeUntil) { @@ -33,7 +33,7 @@ object CommonValidation { feeAssetId: Asset, feeAmount: Long, allowFeeOverdraft: Boolean = false - ) = { + ): Either[ValidationError, T] = { val amountDiff = assetId match { case aid @ IssuedAsset(_) => Portfolio(0, LeaseBalance.empty, Map(aid -> -amount)) case Waves => Portfolio(-amount, LeaseBalance.empty, Map.empty) diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiff.scala b/node/src/main/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiff.scala index 546a0f01b9d..a02c18f5607 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiff.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiff.scala @@ -12,7 +12,8 @@ import com.wavesplatform.state._ import com.wavesplatform.transaction.{Asset, TxVersion} import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.{GenericError, OrderValidationError} -import com.wavesplatform.transaction.assets.exchange.{ExchangeTransaction, Order, OrderType} +import com.wavesplatform.transaction.assets.exchange.{ExchangeTransaction, Order, OrderPriceMode, OrderType} +import com.wavesplatform.transaction.assets.exchange.OrderPriceMode.AssetDecimals object ExchangeTransactionDiff { @@ -21,8 +22,13 @@ object ExchangeTransactionDiff { val seller = tx.sellOrder.senderAddress val assetIds = - List(tx.buyOrder.assetPair.amountAsset, tx.buyOrder.assetPair.priceAsset, tx.sellOrder.assetPair.amountAsset, tx.sellOrder.assetPair.priceAsset).collect { - case asset: IssuedAsset => asset + List( + tx.buyOrder.assetPair.amountAsset, + tx.buyOrder.assetPair.priceAsset, + tx.sellOrder.assetPair.amountAsset, + tx.sellOrder.assetPair.priceAsset + ).collect { case asset: IssuedAsset => + asset }.distinct val assets = assetIds.map(id => id -> blockchain.assetDescription(id)).toMap @@ -31,7 +37,7 @@ object ExchangeTransactionDiff { _ <- Right(()) smartTradesEnabled = blockchain.isFeatureActivated(BlockchainFeatures.SmartAccountTrading) smartAssetsEnabled = blockchain.isFeatureActivated(BlockchainFeatures.SmartAssets) - assetsScripted = assets.values.count(_.flatMap(_.script).isDefined) + assetsScripted = assets.values.count(_.flatMap(_.script).isDefined) _ <- Either.cond( smartAssetsEnabled || assetsScripted == 0, (), @@ -52,12 +58,13 @@ object ExchangeTransactionDiff { } yield (assetsScripted, buyerScripted, sellerScripted) for { - buyerAndSellerScripted <- smartFeaturesChecks() - portfolios <- getPortfolios(blockchain, tx) - tx <- enoughVolume(tx, blockchain) + buyerAndSellerScripted <- smartFeaturesChecks() + portfolios <- getPortfolios(blockchain, tx) + _ <- enoughVolume(tx, blockchain) + _ <- checkOrderPriceModes(tx, blockchain) scripts = { val (assetsScripted, buyerScripted, sellerScripted) = buyerAndSellerScripted - val matcherScripted = Some(tx.sender.toAddress).count(blockchain.hasAccountScript) + val matcherScripted = Some(tx.sender.toAddress).count(blockchain.hasAccountScript) // Don't count before Ride4DApps activation val ordersScripted = Seq(buyerScripted, sellerScripted) @@ -88,8 +95,10 @@ object ExchangeTransactionDiff { }.toEither.leftMap(x => GenericError(x.getMessage)) def orderPrice(order: Order, amountDecimals: Int, priceDecimals: Int) = - if (tx.version >= TxVersion.V3 && order.version < Order.V4) convertPrice(order.price, amountDecimals, priceDecimals) - else Right(order.price) + if (tx.version >= TxVersion.V3 && (order.version < Order.V4 || order.priceMode == AssetDecimals)) + convertPrice(order.price, amountDecimals, priceDecimals) + else + Right(order.price) for { _ <- Either.cond(tx.price != 0L, (), GenericError("price should be > 0")) @@ -101,8 +110,13 @@ object ExchangeTransactionDiff { } val assetIds = - List(tx.buyOrder.assetPair.amountAsset, tx.buyOrder.assetPair.priceAsset, tx.sellOrder.assetPair.amountAsset, tx.sellOrder.assetPair.priceAsset).collect { - case asset: IssuedAsset => asset + List( + tx.buyOrder.assetPair.amountAsset, + tx.buyOrder.assetPair.priceAsset, + tx.sellOrder.assetPair.amountAsset, + tx.sellOrder.assetPair.priceAsset + ).collect { case asset: IssuedAsset => + asset }.distinct val assets = assetIds.map(id => id -> blockchain.assetDescription(id)).toMap @@ -148,7 +162,16 @@ object ExchangeTransactionDiff { } yield Monoid.combineAll(Seq(feeDiff, priceDiff, amountDiff)) } - private def enoughVolume(exTrans: ExchangeTransaction, blockchain: Blockchain): Either[ValidationError, ExchangeTransaction] = { + private[this] def checkOrderPriceModes(tx: ExchangeTransaction, blockchain: Blockchain): Either[GenericError, Unit] = { + def isLegacyModeOrder(order: Order) = order.version >= Order.V4 && order.priceMode != OrderPriceMode.Default + Either.cond( + !Seq(tx.order1, tx.order2).exists(isLegacyModeOrder) || blockchain.isFeatureActivated(BlockchainFeatures.RideV6), + (), + GenericError("Legacy price mode is only available after RideV6 activation") + ) + } + + private def enoughVolume(exTrans: ExchangeTransaction, blockchain: Blockchain): Either[ValidationError, Unit] = { val filledBuy = blockchain.filledVolumeAndFee(exTrans.buyOrder.id()) val filledSell = blockchain.filledVolumeAndFee(exTrans.sellOrder.id()) @@ -189,7 +212,7 @@ object ExchangeTransactionDiff { Left(OrderValidationError(exTrans.sellOrder, s"Too much sell. Already filled volume for the order: ${filledSell.volume}")) else if (!buyFeeValid) Left(OrderValidationError(exTrans.buyOrder, s"Insufficient buy fee")) else if (!sellFeeValid) Left(OrderValidationError(exTrans.sellOrder, s"Insufficient sell fee")) - else Right(exTrans) + else Right(()) } private[diffs] def getSpendAmount( @@ -223,8 +246,7 @@ object ExchangeTransactionDiff { } }.toEither.left.map(x => GenericError(x.getMessage)) - /** - * Calculates fee portfolio from the order (taking into account that in OrderV3 fee can be paid in asset != Waves) + /** Calculates fee portfolio from the order (taking into account that in OrderV3 fee can be paid in asset != Waves) */ private[diffs] def getOrderFeePortfolio(order: Order, fee: Long): Portfolio = Portfolio.build(order.matcherFeeAssetId, fee) diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/EthOrders.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/EthOrders.scala index 144d14431a7..7799dbd6759 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/EthOrders.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/EthOrders.scala @@ -3,16 +3,19 @@ package com.wavesplatform.transaction.assets.exchange import java.math.BigInteger import java.nio.ByteBuffer +import com.google.common.base.CaseFormat import com.wavesplatform.account.{AddressScheme, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.transaction.Asset import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import org.bouncycastle.util.encoders.Hex -import org.web3j.crypto.{ECDSASignature, Sign, StructuredDataEncoder} +import org.web3j.crypto.{ECDSASignature, ECKeyPair, Sign, StructuredDataEncoder} import org.web3j.crypto.Sign.SignatureData import play.api.libs.json.{JsObject, Json} object EthOrders extends App { + private[this] lazy val toSnakeCase = CaseFormat.UPPER_CAMEL.converterTo(CaseFormat.UPPER_UNDERSCORE) + def toEip712Json(order: Order): JsObject = { def encodeAsset(asset: Asset): String = asset match { case IssuedAsset(id) => id.toString @@ -24,6 +27,12 @@ object EthOrders extends App { case OrderType.SELL => "SELL" } + val priceMode = order.priceMode match { + case OrderPriceMode.Default if order.version < Order.V4 => OrderPriceMode.AssetDecimals + case OrderPriceMode.Default => OrderPriceMode.FixedDecimals + case other => other + } + val message = Json.obj( "version" -> order.version.toInt, "matcherPublicKey" -> order.matcherPublicKey.toString, @@ -35,7 +44,8 @@ object EthOrders extends App { "timestamp" -> order.timestamp, "expiration" -> order.expiration, "matcherFee" -> order.matcherFee, - "matcherFeeAssetId" -> encodeAsset(order.matcherFeeAssetId) + "matcherFeeAssetId" -> encodeAsset(order.matcherFeeAssetId), + "priceMode" -> toSnakeCase.convert(priceMode.toString) ) Json.parse(orderDomainJson).as[JsObject] ++ Json.obj("message" -> message) @@ -65,12 +75,22 @@ object EthOrders extends App { PublicKey(ByteStr(signerKey)) } + def signOrder(order: Order, key: ECKeyPair): Array[Byte] = { + val message = hashOrderStruct(order) + val signature = Sign.signMessage(message, key, false) + val buffer = ByteBuffer.allocate(signature.getR.length + signature.getS.length + signature.getV.length) + buffer.put(signature.getR) + buffer.put(signature.getS) + buffer.put(signature.getV) + buffer.array() + } + def decodeSignature(signature: Array[Byte]): SignatureData = { val buffer = ByteBuffer.wrap(signature) val paramSize = buffer.remaining() match { - case 129 => 64 - case 65 => 32 - case other => throw new IllegalArgumentException(s"Unexpected signature length: $other") + case 129 => 64 + case 65 => 32 + case other => throw new IllegalArgumentException(s"Unexpected signature length: $other") } val R = new Array[Byte](paramSize) val S = new Array[Byte](paramSize) @@ -82,81 +102,85 @@ object EthOrders extends App { def orderDomainJson: String = s""" - |{ - | "types": { - | "EIP712Domain": [ - | { - | "name": "name", - | "type": "string" - | }, - | { - | "name": "version", - | "type": "string" - | }, - | { - | "name": "chainId", - | "type": "uint256" - | }, - | { - | "name": "verifyingContract", - | "type": "address" - | } - | ], - | "Order": [ - | { - | "name": "version", - | "type": "int32" - | }, - | { - | "name": "matcherPublicKey", - | "type": "string" - | }, - | { - | "name": "amountAsset", - | "type": "string" - | }, - | { - | "name": "priceAsset", - | "type": "string" - | }, - | { - | "name": "orderType", - | "type": "string" - | }, - | { - | "name": "amount", - | "type": "int64" - | }, - | { - | "name": "price", - | "type": "int64" - | }, - | { - | "name": "timestamp", - | "type": "int64" - | }, - | { - | "name": "expiration", - | "type": "int64" - | }, - | { - | "name": "matcherFee", - | "type": "int64" - | }, - | { - | "name": "matcherFeeAssetId", - | "type": "string" - | } - | ] - | }, - | "primaryType": "Order", - | "domain": { - | "name": "Waves Exchange", - | "version": "1", - | "chainId": ${AddressScheme.current.chainId}, - | "verifyingContract": "0x${Hex.toHexString(Array.fill[Byte](20)(AddressScheme.current.chainId))}" - | }, - | "message": {} - |} - |""".stripMargin + |{ + | "types": { + | "EIP712Domain": [ + | { + | "name": "name", + | "type": "string" + | }, + | { + | "name": "version", + | "type": "string" + | }, + | { + | "name": "chainId", + | "type": "uint256" + | }, + | { + | "name": "verifyingContract", + | "type": "address" + | } + | ], + | "Order": [ + | { + | "name": "version", + | "type": "int32" + | }, + | { + | "name": "matcherPublicKey", + | "type": "string" + | }, + | { + | "name": "amountAsset", + | "type": "string" + | }, + | { + | "name": "priceAsset", + | "type": "string" + | }, + | { + | "name": "orderType", + | "type": "string" + | }, + | { + | "name": "amount", + | "type": "int64" + | }, + | { + | "name": "price", + | "type": "int64" + | }, + | { + | "name": "timestamp", + | "type": "int64" + | }, + | { + | "name": "expiration", + | "type": "int64" + | }, + | { + | "name": "matcherFee", + | "type": "int64" + | }, + | { + | "name": "matcherFeeAssetId", + | "type": "string" + | }, + | { + | "name": "priceMode", + | "type": "string" + | } + | ] + | }, + | "primaryType": "Order", + | "domain": { + | "name": "Waves Exchange", + | "version": "1", + | "chainId": ${AddressScheme.current.chainId}, + | "verifyingContract": "0x${Hex.toHexString(Array.fill[Byte](20)(AddressScheme.current.chainId))}" + | }, + | "message": {} + |} + |""".stripMargin } diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/Order.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/Order.scala index 0a6da4c89f0..120afe73f39 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/Order.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/Order.scala @@ -5,36 +5,62 @@ import scala.util.Try import com.wavesplatform.account.{Address, KeyPair, PrivateKey, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto -import com.wavesplatform.transaction._ +import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.Waves +import com.wavesplatform.transaction.assets.exchange.Order.Version import com.wavesplatform.transaction.assets.exchange.Validation.booleanOperators import com.wavesplatform.transaction.serialization.impl.OrderSerializer import monix.eval.Coeval import play.api.libs.json.{Format, JsObject} -/** - * Order to matcher service for asset exchange +sealed trait OrderAuthentication +object OrderAuthentication { + final case class OrderProofs(key: PublicKey, proofs: Proofs) extends OrderAuthentication + final case class Eip712Signature(signature: ByteStr) extends OrderAuthentication + + def apply(pk: PublicKey): OrderProofs = OrderProofs(pk, Proofs.empty) +} + +/** Order to matcher service for asset exchange */ case class Order( - version: Order.Version, - senderPublicKey: PublicKey, - matcherPublicKey: PublicKey, - assetPair: AssetPair, - orderType: OrderType, - amount: TxAmount, - price: TxAmount, - timestamp: TxTimestamp, - expiration: TxTimestamp, - matcherFee: TxAmount, - matcherFeeAssetId: Asset = Waves, - proofs: Proofs = Proofs.empty, - eip712Signature: Option[ByteStr] = None + version: Version, + orderAuthentication: OrderAuthentication, + matcherPublicKey: PublicKey, + assetPair: AssetPair, + orderType: OrderType, + amount: TxAmount, + price: TxAmount, + timestamp: TxTimestamp, + expiration: TxTimestamp, + matcherFee: TxAmount, + matcherFeeAssetId: Asset = Waves, + priceMode: OrderPriceMode = OrderPriceMode.Default ) extends Proven { - import Order._ + import Order.* + + lazy val senderPublicKey: PublicKey = orderAuthentication match { + case OrderAuthentication.OrderProofs(publicKey, _) => publicKey + case OrderAuthentication.Eip712Signature(signature) => EthOrders.recoverEthSignerKey(this, signature.arr) + } + + val eip712Signature: Option[ByteStr] = orderAuthentication match { + case OrderAuthentication.Eip712Signature(signature) => Some(signature) + case OrderAuthentication.OrderProofs(_, _) => None + } + + val proofs: Proofs = orderAuthentication match { + case OrderAuthentication.OrderProofs(_, proofs) => proofs + case OrderAuthentication.Eip712Signature(_) => Proofs.empty + } val sender: PublicKey = senderPublicKey def senderAddress: Address = sender.toAddress + def withProofs(proofs: Proofs): Order = { + copy(orderAuthentication = OrderAuthentication.OrderProofs(senderPublicKey, proofs)) + } + def isValid(atTime: Long): Validation = { isValidAmount(amount, price) && assetPair.isValid && @@ -46,7 +72,7 @@ case class Order( (matcherFeeAssetId == Waves || version >= Order.V3) :| "matcherFeeAssetId should be waves" && (eip712Signature.isEmpty || version >= Order.V4) :| "eip712Signature available only in V4" && eip712Signature.forall(es => es.size == 65 || es.size == 129) :| "eip712Signature should be of length 65 or 129" && - (eip712Signature.isEmpty || proofs.isEmpty) :| "eip712Signature excludes proofs" + (version >= Order.V4 || priceMode == OrderPriceMode.Default) :| s"price mode should be default for V$version" } def isValidAmount(matchAmount: Long, matchPrice: Long): Validation = { @@ -58,6 +84,10 @@ case class Order( val bodyBytes: Coeval[Array[Byte]] = Coeval.evalOnce(OrderSerializer.bodyBytes(this)) val id: Coeval[ByteStr] = Coeval.evalOnce(ByteStr(crypto.fastHash(bodyBytes()))) val idStr: Coeval[String] = Coeval.evalOnce(id().toString) + + /** + * @note Shouldn't be used for orders >= V4 + */ val bytes: Coeval[Array[Byte]] = Coeval.evalOnce(OrderSerializer.toBytes(this)) def getReceiveAssetId: Asset = orderType match { @@ -74,8 +104,8 @@ case class Order( override def toString: String = { val matcherFeeAssetIdStr = if (version == 3) s" matcherFeeAssetId=${matcherFeeAssetId.fold("Waves")(_.toString)}," else "" - s"OrderV$version(id=${idStr()}, sender=$senderPublicKey, matcher=$matcherPublicKey, pair=$assetPair, tpe=$orderType, amount=$amount, " + - s"price=$price, ts=$timestamp, exp=$expiration, fee=$matcherFee,$matcherFeeAssetIdStr proofs=$proofs)" + s"OrderV$version(id=${idStr()}, sender=$senderPublicKey, matcher=$matcherPublicKey, pair=$assetPair, type=$orderType, amount=$amount, " + + s"price=$price, priceMode=$priceMode, ts=$timestamp, exp=$expiration, fee=$matcherFee,$matcherFeeAssetIdStr, eip712Signature=$eip712Signature, proofs=$proofs)" } } @@ -95,7 +125,7 @@ object Order { val V4: Version = 4.toByte implicit def sign(order: Order, privateKey: PrivateKey): Order = - order.copy(proofs = Proofs(crypto.sign(privateKey, order.bodyBytes()))) + order.withProofs(Proofs(crypto.sign(privateKey, order.bodyBytes()))) def selfSigned( version: TxVersion, @@ -108,10 +138,23 @@ object Order { timestamp: TxTimestamp, expiration: TxTimestamp, matcherFee: TxAmount, - matcherFeeAssetId: Asset = Asset.Waves + matcherFeeAssetId: Asset = Asset.Waves, + priceMode: OrderPriceMode = OrderPriceMode.Default ): Order = - Order(version, sender.publicKey, matcher, assetPair, orderType, amount, price, timestamp, expiration, matcherFee, matcherFeeAssetId) - .signWith(sender.privateKey) + Order( + version, + OrderAuthentication(sender.publicKey), + matcher, + assetPair, + orderType, + amount, + price, + timestamp, + expiration, + matcherFee, + matcherFeeAssetId, + priceMode = priceMode + ).signWith(sender.privateKey) def buy( version: TxVersion, @@ -123,9 +166,10 @@ object Order { timestamp: TxTimestamp, expiration: TxTimestamp, matcherFee: TxAmount, - matcherFeeAssetId: Asset = Waves + matcherFeeAssetId: Asset = Waves, + priceMode: OrderPriceMode = OrderPriceMode.Default ): Order = { - Order.selfSigned(version, sender, matcher, pair, OrderType.BUY, amount, price, timestamp, expiration, matcherFee, matcherFeeAssetId) + Order.selfSigned(version, sender, matcher, pair, OrderType.BUY, amount, price, timestamp, expiration, matcherFee, matcherFeeAssetId, priceMode) } def sell( @@ -138,9 +182,10 @@ object Order { timestamp: TxTimestamp, expiration: TxTimestamp, matcherFee: TxAmount, - matcherFeeAssetId: Asset = Waves + matcherFeeAssetId: Asset = Waves, + priceMode: OrderPriceMode = OrderPriceMode.Default ): Order = { - Order.selfSigned(version, sender, matcher, pair, OrderType.SELL, amount, price, timestamp, expiration, matcherFee, matcherFeeAssetId) + Order.selfSigned(version, sender, matcher, pair, OrderType.SELL, amount, price, timestamp, expiration, matcherFee, matcherFeeAssetId, priceMode) } def parseBytes(version: Version, bytes: Array[Byte]): Try[Order] = diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/OrderJson.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/OrderJson.scala index 6b113fdc3ef..360039053d1 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/OrderJson.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/OrderJson.scala @@ -8,6 +8,7 @@ import com.wavesplatform.common.utils.Base58 import com.wavesplatform.crypto.SignatureLength import com.wavesplatform.transaction.{Asset, Proofs, TxVersion} import com.wavesplatform.transaction.Asset.Waves +import com.wavesplatform.transaction.assets.exchange.OrderPriceMode.{AssetDecimals, FixedDecimals} import com.wavesplatform.utils.EthEncoding import play.api.libs.json._ @@ -58,18 +59,30 @@ object OrderJson { version: Option[Byte] ): Order = { - val eproofs = + val proofsValue = proofs .map(p => Proofs(p.map(ByteStr.apply).toList)) .orElse(signature.map(s => Proofs(List(ByteStr(s))))) .getOrElse(Proofs.empty) - val vrsn: Byte = version.getOrElse(if (eproofs.proofs.size == 1 && eproofs.proofs.head.arr.length == SignatureLength) 1 else 2) - Order(vrsn, sender, matcher, assetPair, orderType, amount, price, timestamp, expiration, matcherFee, proofs = eproofs) + val versionValue: Byte = version.getOrElse(if (proofsValue.proofs.size == 1 && proofsValue.proofs.head.arr.length == SignatureLength) 1 else 2) + Order( + versionValue, + OrderAuthentication.OrderProofs(sender, proofsValue), + matcher, + assetPair, + orderType, + amount, + price, + timestamp, + expiration, + matcherFee, + priceMode = OrderPriceMode.Default + ) } def readOrderV3V4( - sender: PublicKey, + sender: Option[PublicKey], matcher: PublicKey, assetPair: AssetPair, orderType: OrderType, @@ -82,30 +95,26 @@ object OrderJson { proofs: Option[Array[Array[Byte]]], version: TxVersion, matcherFeeAssetId: Asset, - eip712Signature: Option[Array[Byte]] + eip712Signature: Option[Array[Byte]], + priceMode: OrderPriceMode ): Order = { - - val eproofs = - proofs - .map(p => Proofs(p.map(ByteStr.apply).toIndexedSeq)) - .orElse(signature.map(s => Proofs(ByteStr(s)))) - .getOrElse(Proofs.empty) - - Order( - version, - sender, - matcher, - assetPair, - orderType, - amount, - price, - timestamp, - expiration, - matcherFee, - matcherFeeAssetId, - eproofs, - eip712Signature.map(ByteStr(_)) - ) + val senderCredentials = eip712Signature match { + case Some(value) => + OrderAuthentication.Eip712Signature(ByteStr(value)) + + case None => + val proofsValue = proofs + .map(p => Proofs(p.map(ByteStr.apply).toIndexedSeq)) + .orElse(signature.map(s => Proofs(ByteStr(s)))) + .getOrElse(Proofs.empty) + + OrderAuthentication.OrderProofs( + sender.getOrElse(throw new IllegalArgumentException("Either senderPublicKey or eip712Signature should be provided")), + proofsValue + ) + } + + Order(version, senderCredentials, matcher, assetPair, orderType, amount, price, timestamp, expiration, matcherFee, matcherFeeAssetId, priceMode) } private val assetReads: Reads[Asset] = { @@ -127,6 +136,13 @@ object OrderJson { implicit val orderTypeReads: Reads[OrderType] = JsPath.read[String].map(OrderType.apply) + implicit val priceModeReads: Reads[OrderPriceMode] = + JsPath.read[String].flatMapResult { + case "assetDecimals" => JsSuccess(AssetDecimals) + case "fixedDecimals" => JsSuccess(FixedDecimals) + case other => JsError(s"Unexpected order price mode: $other") + } + private val orderV1V2Reads: Reads[Order] = { val r = (JsPath \ "senderPublicKey").read[PublicKey](accountPublicKeyReads) and (JsPath \ "matcherPublicKey").read[PublicKey](accountPublicKeyReads) and @@ -144,7 +160,7 @@ object OrderJson { } private val orderV3V4Reads: Reads[Order] = { - val r = (JsPath \ "senderPublicKey").read[PublicKey](accountPublicKeyReads) and + val r = (JsPath \ "senderPublicKey").readNullable[PublicKey](accountPublicKeyReads) and (JsPath \ "matcherPublicKey").read[PublicKey](accountPublicKeyReads) and (JsPath \ "assetPair").read[AssetPair] and (JsPath \ "orderType").read[OrderType] and @@ -161,7 +177,9 @@ object OrderJson { .map(arrOpt => Asset.fromCompatId(arrOpt.map(ByteStr(_)))) and (JsPath \ "eip712Signature") .readNullable[String] - .map(_.map(EthEncoding.toBytes)) + .map(_.map(EthEncoding.toBytes)) and + (JsPath \ "priceMode") + .readWithDefault[OrderPriceMode](OrderPriceMode.Default) r(readOrderV3V4 _) } @@ -169,12 +187,12 @@ object OrderJson { implicit val orderReads: Reads[Order] = { case jsOrder @ JsObject(map) => map.getOrElse("version", JsNumber(1)) match { - case JsNumber(x) if x.byteValue >= Order.V3 => orderV3V4Reads.reads(jsOrder) - case _ => orderV1V2Reads.reads(jsOrder) + case JsNumber(n) if n == 1 || n == 2 => orderV1V2Reads.reads(jsOrder) + case JsNumber(n) if n >= 3 => orderV3V4Reads.reads(jsOrder) + case v => JsError(s"Invalid version: $v") } - case invalidOrder => JsError(s"Can't parse invalid order $invalidOrder") + case invalidOrder => JsError(s"Order object expected: $invalidOrder") } implicit val orderFormat: Format[Order] = Format(orderReads, Writes[Order](_.json())) - } diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/OrderPriceMode.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/OrderPriceMode.scala new file mode 100644 index 00000000000..98d308707b7 --- /dev/null +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/OrderPriceMode.scala @@ -0,0 +1,30 @@ +package com.wavesplatform.transaction.assets.exchange + +import play.api.libs.json._ + +sealed trait OrderPriceMode { + private[OrderPriceMode] val jsonName: String = { + val modeStr = this.toString.ensuring(_.nonEmpty) + modeStr.updated(0, modeStr.charAt(0).toLower) + } +} + +object OrderPriceMode { + case object Default extends OrderPriceMode + case object AssetDecimals extends OrderPriceMode + case object FixedDecimals extends OrderPriceMode + + private[this] val byJsonName = Seq(Default, AssetDecimals, FixedDecimals).map(v => v.jsonName -> v).toMap + + implicit val jsonFormat: Format[OrderPriceMode] = Format( + Reads { + case JsNull => JsSuccess(Default) + case JsString(mode) if byJsonName.contains(mode) => JsSuccess(byJsonName(mode)) + case other => JsError(s"Invalid mode: $other") + }, + Writes { + case Default => JsNull + case other => JsString(other.jsonName) + } + ) +} diff --git a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/OrderSerializer.scala b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/OrderSerializer.scala index 8c3b08f6ee6..80727bf0e75 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/OrderSerializer.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/OrderSerializer.scala @@ -8,14 +8,14 @@ import com.google.common.primitives.{Bytes, Longs} import com.wavesplatform.protobuf.transaction.PBOrders import com.wavesplatform.protobuf.utils.PBUtils import com.wavesplatform.serialization.ByteBufferOps +import com.wavesplatform.transaction.assets.exchange.* import com.wavesplatform.transaction.Proofs -import com.wavesplatform.transaction.assets.exchange.{AssetPair, Order, OrderType} import com.wavesplatform.utils.EthEncoding import play.api.libs.json.{JsObject, Json} object OrderSerializer { def toJson(order: Order): JsObject = { - import order._ + import order.* Json.obj( "version" -> version, "id" -> idStr(), @@ -32,11 +32,12 @@ object OrderSerializer { "signature" -> proofs.toSignature.toString, "proofs" -> proofs.proofs.map(_.toString) ) ++ (if (version >= Order.V3) Json.obj("matcherFeeAssetId" -> matcherFeeAssetId) else JsObject.empty) ++ - (if (version >= Order.V4) Json.obj("eip712Signature" -> eip712Signature.map(bs => EthEncoding.toHexString(bs.arr))) else JsObject.empty) // TODO: Should it be hex or base58? + (if (version >= Order.V4) Json.obj("eip712Signature" -> eip712Signature.map(bs => EthEncoding.toHexString(bs.arr)), "priceMode" -> priceMode) + else JsObject.empty) // TODO: Should it be hex or base58? } def bodyBytes(order: Order): Array[Byte] = { - import order._ + import order.* version match { case Order.V1 => @@ -82,15 +83,20 @@ object OrderSerializer { ) case _ => - PBUtils.encodeDeterministic(PBOrders.protobuf(order.copy(proofs = Proofs.empty))) + val orderWithoutProofs = order.orderAuthentication match { + case OrderAuthentication.OrderProofs(_, _) => order.withProofs(Proofs.empty) + case OrderAuthentication.Eip712Signature(_) => order // Keep original signature + } + PBUtils.encodeDeterministic(PBOrders.protobuf(orderWithoutProofs)) } } def toBytes(ord: Order): Array[Byte] = { - import ord._ - (version: @unchecked) match { + import ord.* + version match { case Order.V1 => Bytes.concat(this.bodyBytes(ord), proofs.toSignature.arr) case Order.V2 | Order.V3 => Bytes.concat(this.bodyBytes(ord), proofs.bytes()) + case other => throw new IllegalArgumentException(s"Couldn't serialize OrderV$other") } } @@ -105,23 +111,35 @@ object OrderSerializer { val timestamp = buf.getLong val expiration = buf.getLong val matcherFee = buf.getLong - Order(version, sender, matcher, assetPair, orderType, amount, price, timestamp, expiration, matcherFee) + Order( + version, + OrderAuthentication(sender), + matcher, + assetPair, + orderType, + amount, + price, + timestamp, + expiration, + matcherFee, + priceMode = OrderPriceMode.Default + ) } version match { case Order.V1 => val buf = ByteBuffer.wrap(bytes) - parseCommonPart(buf).copy(proofs = Proofs(buf.getSignature)) + parseCommonPart(buf).withProofs(Proofs(buf.getSignature)) case Order.V2 => require(bytes(0) == version, "order version mismatch") val buf = ByteBuffer.wrap(bytes, 1, bytes.length - 1) - parseCommonPart(buf).copy(proofs = buf.getProofs) + parseCommonPart(buf).withProofs(buf.getProofs) case Order.V3 => require(bytes(0) == version, "order version mismatch") val buf = ByteBuffer.wrap(bytes, 1, bytes.length - 1) - parseCommonPart(buf).copy(matcherFeeAssetId = buf.getAsset, proofs = buf.getProofs) + parseCommonPart(buf).copy(matcherFeeAssetId = buf.getAsset).withProofs(buf.getProofs) case _ => throw new IllegalArgumentException(s"Unsupported order version: $version") diff --git a/node/src/test/scala/com/wavesplatform/TransactionGen.scala b/node/src/test/scala/com/wavesplatform/TransactionGen.scala index d1f7f251bd9..3715f9309f2 100644 --- a/node/src/test/scala/com/wavesplatform/TransactionGen.scala +++ b/node/src/test/scala/com/wavesplatform/TransactionGen.scala @@ -1,15 +1,18 @@ package com.wavesplatform +import scala.concurrent.duration.* +import scala.util.Random + import com.wavesplatform.account.* import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.ValidationError import com.wavesplatform.lang.directives.values.{V3, V6} -import com.wavesplatform.lang.script.v1.ExprScript import com.wavesplatform.lang.script.{ContractScript, Script} +import com.wavesplatform.lang.script.v1.ExprScript +import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader} import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.testing.{ScriptGen, TypedScriptGen} -import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader} import com.wavesplatform.settings.{Constants, FunctionalitySettings} import com.wavesplatform.state.* import com.wavesplatform.state.diffs.ENOUGH_AMT @@ -18,21 +21,18 @@ import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.assets.* import com.wavesplatform.transaction.assets.exchange.* import com.wavesplatform.transaction.lease.* -import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.smart.{InvokeExpressionTransaction, InvokeScriptTransaction, SetScriptTransaction} +import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.transfer.* import com.wavesplatform.transaction.transfer.MassTransferTransaction.{MaxTransferCount, ParsedTransfer} import com.wavesplatform.transaction.utils.{EthTxGenerator, Signed} import com.wavesplatform.transaction.utils.EthConverters.* import com.wavesplatform.transaction.utils.EthTxGenerator.Arg -import org.scalacheck.Gen.{alphaLowerChar, alphaUpperChar, frequency, numChar} import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.Gen.{alphaLowerChar, alphaUpperChar, frequency, numChar} import org.scalatest.Suite import org.web3j.crypto.ECKeyPair -import scala.concurrent.duration.* -import scala.util.Random - trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: Suite => val ScriptExtraFee = 400000L @@ -41,9 +41,9 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: def byteArrayGen(length: Int): Gen[Array[Byte]] = Gen.containerOfN[Array, Byte](length, Arbitrary.arbitrary[Byte]) def stringGen(length: Int): Gen[String] = Gen.containerOfN[Array, Char](length, Arbitrary.arbitrary[Char]).map(new String(_)) - val bytes32gen: Gen[Array[Byte]] = byteArrayGen(32) - val bytes64gen: Gen[Array[Byte]] = byteArrayGen(64) - val attachmentGen: Gen[ByteStr] = bytes32gen.map(ByteStr(_)) + val bytes32gen: Gen[Array[Byte]] = byteArrayGen(32) + val bytes64gen: Gen[Array[Byte]] = byteArrayGen(64) + val attachmentGen: Gen[ByteStr] = bytes32gen.map(ByteStr(_)) def genBoundedBytes(minSize: Int, maxSize: Int): Gen[Array[Byte]] = for { @@ -61,9 +61,7 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: } } - val accountGen: Gen[KeyPair] = bytes32gen.map(seed => - KeyPair(seed) - ) + val accountGen: Gen[KeyPair] = bytes32gen.map(seed => KeyPair(seed)) val ethAccountGen: Gen[ECKeyPair] = accountGen.map(_.toEthKeyPair) @@ -155,7 +153,18 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: timestamp <- timestampGen script1 <- scriptGen script2 <- scriptGen - issue = IssueTransaction(TxVersion.V2, sender.publicKey, assetName, description, quantity, decimals, reissuable = true, Some(script1), iFee, timestamp) + issue = IssueTransaction( + TxVersion.V2, + sender.publicKey, + assetName, + description, + quantity, + decimals, + reissuable = true, + Some(script1), + iFee, + timestamp + ) .signWith(sender.privateKey) setAssetScript = SetAssetScriptTransaction .selfSigned(1.toByte, sender, IssuedAsset(issue.id()), Some(script2), 1 * Constants.UnitsInWave + ScriptExtraFee, timestamp) @@ -260,7 +269,7 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: def leaseGen(sender: KeyPair, timestamp: Long): Gen[LeaseTransaction] = for { (_, amount, fee, _, recipient) <- leaseParamGen - version <- Gen.oneOf(1.toByte, 2.toByte, 3.toByte) + version <- Gen.oneOf(1.toByte, 2.toByte, 3.toByte) } yield LeaseTransaction.selfSigned(version, sender, recipient.toAddress, amount, fee, timestamp).explicitGet() val leaseGen: Gen[LeaseTransaction] = leaseAndCancelGen.map(_._1) @@ -341,7 +350,19 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: val transferV2Gen: Gen[TransferTransaction] = (for { (assetId, sender, recipient, amount, timestamp, feeAssetId, feeAmount, attachment) <- transferParamGen proofs <- proofsGen - } yield TransferTransaction(2.toByte, sender.publicKey, recipient, assetId, amount, feeAssetId, feeAmount, attachment, timestamp, proofs, recipient.chainId)) + } yield TransferTransaction( + 2.toByte, + sender.publicKey, + recipient, + assetId, + amount, + feeAssetId, + feeAmount, + attachment, + timestamp, + proofs, + recipient.chainId + )) .label("VersionedTransferTransaction") def versionedTransferGenP(sender: PublicKey, recipient: AddressOrAlias, proofs: Proofs): Gen[TransferTransaction] = @@ -362,7 +383,9 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: val selfTransferGen: Gen[TransferTransaction] = for { (assetId, sender, _, amount, timestamp, feeAssetId, feeAmount, attachment) <- transferParamGen - } yield TransferTransaction.selfSigned(1.toByte, sender, sender.toAddress, assetId, amount, feeAssetId, feeAmount, attachment, timestamp).explicitGet() + } yield TransferTransaction + .selfSigned(1.toByte, sender, sender.toAddress, assetId, amount, feeAssetId, feeAmount, attachment, timestamp) + .explicitGet() val massTransferGen: Gen[MassTransferTransaction] = massTransferGen(MaxTransferCount) @@ -457,10 +480,12 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: .explicitGet() } yield tx - /** - * @param issueQuantity must be positive - * @param reissueQuantity must be positive - * @param burnQuantity must be positive + /** @param issueQuantity + * must be positive + * @param reissueQuantity + * must be positive + * @param burnQuantity + * must be positive */ def issueReissueBurnGeneratorP( issueQuantity: Long, @@ -473,9 +498,9 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: (_, assetName, description, _, decimals, reissuable, iFee, timestamp) <- issueParamGen reissuable2 <- Arbitrary.arbitrary[Boolean] fee <- smallFeeGen - issue <- createLegacyIssue(sender, assetName, description, issueQuantity, decimals, reissuable, iFee, timestamp) - reissue <- createReissue(sender, issue.asset, reissueQuantity, reissuable2, fee, timestamp) - burn <- createBurn(sender, issue.asset, burnQuantity, fee, timestamp) + issue <- createLegacyIssue(sender, assetName, description, issueQuantity, decimals, reissuable, iFee, timestamp) + reissue <- createReissue(sender, issue.asset, reissueQuantity, reissuable2, fee, timestamp) + burn <- createBurn(sender, issue.asset, burnQuantity, fee, timestamp) } yield (issue, reissue, burn) } @@ -483,8 +508,9 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: (sender, assetName, description, quantity, decimals, _, iFee, timestamp) <- issueParamGen fee <- smallFeeGen } yield { - val issue = IssueTransaction(TxVersion.V1, sender.publicKey, assetName, description, quantity, decimals, reissuable = true, script = None, iFee, timestamp) - .signWith(sender.privateKey) + val issue = + IssueTransaction(TxVersion.V1, sender.publicKey, assetName, description, quantity, decimals, reissuable = true, script = None, iFee, timestamp) + .signWith(sender.privateKey) val reissue1 = ReissueTransaction.selfSigned(1.toByte, sender, issue.asset, quantity, reissuable = false, fee, timestamp).explicitGet() val reissue2 = @@ -532,7 +558,10 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: val reissueGen: Gen[ReissueTransaction] = issueReissueBurnGen.map(_._2) val burnGen: Gen[BurnTransaction] = issueReissueBurnGen.map(_._3) - def sponsorFeeCancelSponsorFeeGen(sender: KeyPair, reducedFee: Boolean = true): Gen[(IssueTransaction, SponsorFeeTransaction, SponsorFeeTransaction, SponsorFeeTransaction)] = + def sponsorFeeCancelSponsorFeeGen( + sender: KeyPair, + reducedFee: Boolean = true + ): Gen[(IssueTransaction, SponsorFeeTransaction, SponsorFeeTransaction, SponsorFeeTransaction)] = for { (_, assetName, description, quantity, decimals, reissuable, iFee, timestamp) <- issueParamGen issue = IssueTransaction( @@ -550,7 +579,7 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: minFee <- smallFeeGen minFee1 <- smallFeeGen assetId = issue.asset - fee = (if (reducedFee) 0.001 * Constants.UnitsInWave else 1 * Constants.UnitsInWave.toDouble).toLong + fee = (if (reducedFee) 0.001 * Constants.UnitsInWave else 1 * Constants.UnitsInWave.toDouble).toLong } yield ( issue, SponsorFeeTransaction.selfSigned(1.toByte, sender, assetId, Some(minFee), fee, timestamp).explicitGet(), @@ -704,15 +733,27 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: for { (_, genMatcher, _, _, amount1, price, timestamp, expiration, genMatcherFee) <- orderParamGen amount2: Long <- matcherAmountGen - matchedAmount: Long <- Gen.choose(Math.min(amount1, amount2) / 2000, Math.min(amount1, amount2) / 1000) + matchedAmount: Long <- Gen.choose(Math.min(amount1, amount2) / 2000, Math.min(amount1, amount2) / 1000) assetPair = AssetPair(amountAssetId, priceAssetId) } yield { val matcherFee = fixedMatcherFee.getOrElse(genMatcherFee) val matcher = fixedMatcher.getOrElse(genMatcher) - val o1 = Order.buy(1: Byte, buyer, matcher.publicKey, assetPair, amount1, price, timestamp, expiration, matcherFee) - val o2 = Order.sell(1: Byte, seller, matcher.publicKey, assetPair, amount2, price, timestamp, expiration, matcherFee) - val buyFee = (BigInt(matcherFee) * BigInt(matchedAmount) / BigInt(amount1)).longValue - val sellFee = (BigInt(matcherFee) * BigInt(matchedAmount) / BigInt(amount2)).longValue + val o1 = + Order.buy(1: Byte, buyer, matcher.publicKey, assetPair, amount1, price, timestamp, expiration, matcherFee, priceMode = OrderPriceMode.Default) + val o2 = Order.sell( + 1: Byte, + seller, + matcher.publicKey, + assetPair, + amount2, + price, + timestamp, + expiration, + matcherFee, + priceMode = OrderPriceMode.Default + ) + val buyFee = (BigInt(matcherFee) * BigInt(matchedAmount) / BigInt(amount1)).longValue + val sellFee = (BigInt(matcherFee) * BigInt(matchedAmount) / BigInt(amount2)).longValue val trans = ExchangeTransaction(1.toByte, o1, o2, matchedAmount, price, buyFee, sellFee, (buyFee + sellFee) / 2, expiration - 100, Proofs.empty, chainId) .signWith(matcher.privateKey) @@ -832,8 +873,8 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: val randomTransactionGen: Gen[Transaction & ProvenTransaction] = (for { tr <- transferV1Gen - (is, ri, bu) <- issueReissueBurnGen.retryUntil { - case (i, r, b) => i.version == 1 && r.version == 1 && b.version == 1 + (is, ri, bu) <- issueReissueBurnGen.retryUntil { case (i, r, b) => + i.version == 1 && r.version == 1 && b.version == 1 } ca <- createAliasGen.retryUntil(_.version == 1) xt <- exchangeTransactionGen @@ -919,8 +960,9 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: sender <- sender.fold(accountGen)(Gen.const) size <- Gen.choose(0, maxEntryCount) maxEntrySize = if (useForScript) 200 else (DataTransaction.MaxBytes - 122) / (size max 1) min DataEntry.MaxValueSize - data <- if (useForScript) Gen.listOfN(size, dataEntryGen(maxEntrySize, dataScriptsKeyGen, withDeleteEntry)) - else Gen.listOfN(size, dataEntryGen(maxEntrySize)) + data <- + if (useForScript) Gen.listOfN(size, dataEntryGen(maxEntrySize, dataScriptsKeyGen, withDeleteEntry)) + else Gen.listOfN(size, dataEntryGen(maxEntrySize)) uniq = data.foldRight(List.empty[DataEntry[?]]) { (e, es) => if (es.exists(_.key == e.key)) es else e :: es } @@ -993,5 +1035,4 @@ trait TransactionGenBase extends ScriptGen with TypedScriptGen with NTPTime { _: ) } -trait TransactionGen extends TransactionGenBase { _: Suite => -} +trait TransactionGen extends TransactionGenBase { _: Suite => } diff --git a/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala b/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala index b5b41090059..f79e8a2f340 100644 --- a/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala @@ -1,5 +1,8 @@ package com.wavesplatform.http +import scala.concurrent.Future +import scala.util.Random + import com.wavesplatform.BlockchainStubHelpers import com.wavesplatform.account.{AddressScheme, KeyPair} import com.wavesplatform.api.common.CommonTransactionsApi @@ -11,20 +14,15 @@ import com.wavesplatform.lang.v1.traits.domain.{Lease, Recipient} import com.wavesplatform.network.TransactionPublisher import com.wavesplatform.state.{AccountScriptInfo, Blockchain} import com.wavesplatform.test.TestTime -import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} +import com.wavesplatform.transaction.{Asset, Proofs, TxHelpers, TxVersion} import com.wavesplatform.transaction.TxValidationError.GenericError -import com.wavesplatform.transaction.assets.exchange.{AssetPair, Order, OrderType} import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.smart.script.ScriptCompiler import com.wavesplatform.transaction.smart.script.trace.{AccountVerifierTrace, TracedResult} -import com.wavesplatform.transaction.{Asset, Proofs, TxHelpers, TxVersion} -import com.wavesplatform.utils.EthHelpers +import com.wavesplatform.utils.{EthEncoding, EthHelpers} import com.wavesplatform.wallet.Wallet import org.scalamock.scalatest.PathMockFactory -import play.api.libs.json.{JsObject, JsValue, Json} - -import scala.concurrent.Future -import scala.util.Random +import play.api.libs.json.{JsObject, Json, JsValue} class TransactionBroadcastSpec extends RouteSpec("/transactions") @@ -61,102 +59,91 @@ class TransactionBroadcastSpec val route = transactionsApiRoute.copy(blockchain = blockchain, transactionPublisher = transactionPublisher).route - val ethBuyOrder = Order( - Order.V4, - TestEthOrdersPublicKey, - TxHelpers.matcher.publicKey, - AssetPair(IssuedAsset(ByteStr(EthStubBytes32)), Waves), - OrderType.BUY, - 1, - 100L, - 1, - 123, - 100000, - Waves, - eip712Signature = EthSignature( - "0xe5ff562bfb0296e95b631365599c87f1c5002597bf56a131f289765275d2580f5344c62999404c37cd858ea037328ac91eca16ad1ce69c345ebb52fde70b66251c" - ) - ) - - val ethSellOrder = Order( - Order.V4, - TestEthOrdersPublicKey, - TxHelpers.matcher.publicKey, - AssetPair(IssuedAsset(ByteStr(EthStubBytes32)), Waves), - OrderType.SELL, - 1, - 100L, - 1, - 123, - 100000, - Waves, - eip712Signature = EthSignature( - "0xc8ba2bdafd27742546b3be34883efc51d6cdffbb235798d7b51876c6854791f019b0522d7a39b6f2087cba46ae86919b71a2d9d7920dfc8e00246d8f02a258f21b" - ) - ) - + import com.wavesplatform.transaction.assets.exchange.EthOrderSpec.{ethBuyOrder, ethSellOrder} val transaction = TxHelpers.exchange(ethBuyOrder, ethSellOrder, TxVersion.V3, 100) testTime.setTime(100) + val validResponseJson = + s"""{ + | "type" : 7, + | "id" : "${transaction.id()}", + | "sender" : "3FrCwv8uFRxQazhX6Lno45aZ68Bof6ScaeF", + | "senderPublicKey" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", + | "fee" : 1000000, + | "feeAssetId" : null, + | "timestamp" : 100, + | "proofs" : [ "${transaction.signature}" ], + | "version" : 3, + | "chainId" : 69, + | "order1" : { + | "version" : 4, + | "id" : "${ethBuyOrder.id()}", + | "sender" : "${ethBuyOrder.senderPublicKey.toAddress}", + | "senderPublicKey" : "${ethBuyOrder.senderPublicKey}", + | "matcherPublicKey" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", + | "assetPair" : { + | "amountAsset" : "5fQPsn8hoaVddFG26cWQ5QFdqxWtUPNaZ9zH2E6LYzFn", + | "priceAsset" : null + | }, + | "orderType" : "buy", + | "amount" : 1, + | "price" : 100, + | "timestamp" : 1, + | "expiration" : 123, + | "matcherFee" : 100000, + | "signature" : "", + | "proofs" : [ ], + | "matcherFeeAssetId" : null, + | "eip712Signature" : "${EthEncoding.toHexString(ethBuyOrder.eip712Signature.get.arr)}", + | "priceMode" : null + | }, + | "order2" : { + | "version" : 4, + | "id" : "${ethSellOrder.id()}", + | "sender" : "${ethSellOrder.senderPublicKey.toAddress}", + | "senderPublicKey" : "${ethSellOrder.senderPublicKey}", + | "matcherPublicKey" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", + | "assetPair" : { + | "amountAsset" : "5fQPsn8hoaVddFG26cWQ5QFdqxWtUPNaZ9zH2E6LYzFn", + | "priceAsset" : null + | }, + | "orderType" : "sell", + | "amount" : 1, + | "price" : 100, + | "timestamp" : 1, + | "expiration" : 123, + | "matcherFee" : 100000, + | "signature" : "", + | "proofs" : [ ], + | "matcherFeeAssetId" : null, + | "eip712Signature" : "${EthEncoding.toHexString(ethSellOrder.eip712Signature.get.arr)}", + | "priceMode" : null + | }, + | "amount" : 1, + | "price" : 100, + | "buyMatcherFee" : 100000, + | "sellMatcherFee" : 100000 + |} + |""".stripMargin + Post(routePath("/broadcast"), transaction.json()) ~> route ~> check { - responseAs[JsObject] should matchJson(s"""{ - | "type" : 7, - | "id" : "${transaction.id()}", - | "sender" : "3FrCwv8uFRxQazhX6Lno45aZ68Bof6ScaeF", - | "senderPublicKey" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", - | "fee" : 1000000, - | "feeAssetId" : null, - | "timestamp" : 100, - | "proofs" : [ "${transaction.signature}" ], - | "version" : 3, - | "chainId" : 69, - | "order1" : { - | "version" : 4, - | "id" : "${ethBuyOrder.id()}", - | "sender" : "3FzoJXUesFqzf4nmMYejpUDYmFJvkwEiQG6", - | "senderPublicKey" : "5BQPcwDXaZexgonPb8ipDrLRXY3RHn1kFLP9fqp1s6M6xiRhC4LvsAq2HueXCMzkpuXsrLnuBA3SdkJyuhNZXMCd", - | "matcherPublicKey" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", - | "assetPair" : { - | "amountAsset" : "5fQPsn8hoaVddFG26cWQ5QFdqxWtUPNaZ9zH2E6LYzFn", - | "priceAsset" : null - | }, - | "orderType" : "buy", - | "amount" : 1, - | "price" : 100, - | "timestamp" : 1, - | "expiration" : 123, - | "matcherFee" : 100000, - | "signature" : "", - | "proofs" : [ ], - | "matcherFeeAssetId" : null, - | "eip712Signature" : "0xe5ff562bfb0296e95b631365599c87f1c5002597bf56a131f289765275d2580f5344c62999404c37cd858ea037328ac91eca16ad1ce69c345ebb52fde70b66251c" - | }, - | "order2" : { - | "version" : 4, - | "id" : "${ethSellOrder.id()}", - | "sender" : "3FzoJXUesFqzf4nmMYejpUDYmFJvkwEiQG6", - | "senderPublicKey" : "5BQPcwDXaZexgonPb8ipDrLRXY3RHn1kFLP9fqp1s6M6xiRhC4LvsAq2HueXCMzkpuXsrLnuBA3SdkJyuhNZXMCd", - | "matcherPublicKey" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", - | "assetPair" : { - | "amountAsset" : "5fQPsn8hoaVddFG26cWQ5QFdqxWtUPNaZ9zH2E6LYzFn", - | "priceAsset" : null - | }, - | "orderType" : "sell", - | "amount" : 1, - | "price" : 100, - | "timestamp" : 1, - | "expiration" : 123, - | "matcherFee" : 100000, - | "signature" : "", - | "proofs" : [ ], - | "matcherFeeAssetId" : null, - | "eip712Signature" : "0xc8ba2bdafd27742546b3be34883efc51d6cdffbb235798d7b51876c6854791f019b0522d7a39b6f2087cba46ae86919b71a2d9d7920dfc8e00246d8f02a258f21b" - | }, - | "amount" : 1, - | "price" : 100, - | "buyMatcherFee" : 100000, - | "sellMatcherFee" : 100000 - |} - |""".stripMargin) + responseAs[JsObject] should matchJson(validResponseJson) + } + + def removeFields(json: JsObject, fields: String*): JsObject = { + val order1 = (json \ "order1").as[JsObject] + val order2 = (json \ "order2").as[JsObject] + json + ("order1" -> fields.foldLeft(order1)(_ - _)) + ("order2" -> fields.foldLeft(order2)(_ - _)) + } + + Post(routePath("/broadcast"), removeFields(transaction.json(), "senderPublicKey")) ~> route ~> check { + responseAs[JsObject] should matchJson(validResponseJson) + } + + Post(routePath("/broadcast"), removeFields(transaction.json(), "senderPublicKey", "eip712Signature")) ~> route ~> check { + responseAs[JsObject] should matchJson("""{ + | "error" : 199, + | "message" : "Either senderPublicKey or eip712Signature should be provided" + |}""".stripMargin) } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala index cd824ff31e6..52a0d7bff02 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala @@ -19,17 +19,17 @@ import com.wavesplatform.lang.v1.compiler.Terms.FUNCTION_CALL import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 import com.wavesplatform.lang.v1.evaluator.FunctionIds.THROW import com.wavesplatform.mining.MiningConstraint -import com.wavesplatform.settings.{Constants, FunctionalitySettings, TestFunctionalitySettings} +import com.wavesplatform.settings.{Constants, FunctionalitySettings, TestFunctionalitySettings, WavesSettings} import com.wavesplatform.state._ import com.wavesplatform.state.diffs.ExchangeTransactionDiff.getOrderFeePortfolio import com.wavesplatform.state.diffs.TransactionDiffer.TransactionValidationError import com.wavesplatform.test._ -import com.wavesplatform.test.PropSpec import com.wavesplatform.transaction._ import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.AccountBalanceError import com.wavesplatform.transaction.assets.IssueTransaction import com.wavesplatform.transaction.assets.exchange._ +import com.wavesplatform.transaction.assets.exchange.OrderPriceMode.{AssetDecimals, FixedDecimals, Default as DefaultPriceMode} import com.wavesplatform.transaction.smart.SetScriptTransaction import com.wavesplatform.transaction.smart.script.ScriptCompiler import com.wavesplatform.transaction.transfer.{MassTransferTransaction, TransferTransaction} @@ -774,9 +774,9 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w property(s"Exchange transaction with scripted matcher and orders needs extra fee ($ScriptExtraFee)") { val allValidP = smartTradePreconditions( - scriptGen("Order", true), - scriptGen("Order", true), - scriptGen("ExchangeTransaction", true) + scriptGen("Order", v = true), + scriptGen("Order", v = true), + scriptGen("ExchangeTransaction", v = true) ) forAll(allValidP) { @@ -803,9 +803,9 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w property("ExchangeTransactions valid if all scripts succeeds") { val allValidP = smartTradePreconditions( - scriptGen("Order", true), - scriptGen("Order", true), - scriptGen("ExchangeTransaction", true) + scriptGen("Order", v = true), + scriptGen("Order", v = true), + scriptGen("ExchangeTransaction", v = true) ) forAll(allValidP) { @@ -823,9 +823,9 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w property("ExchangeTransactions invalid if buyer scripts fails") { val failedOrderScript = smartTradePreconditions( - scriptGen("Order", false), - scriptGen("Order", true), - scriptGen("ExchangeTransaction", true) + scriptGen("Order", v = false), + scriptGen("Order", v = true), + scriptGen("ExchangeTransaction", v = true) ) forAll(failedOrderScript) { @@ -837,9 +837,9 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w property("ExchangeTransactions invalid if seller scripts fails") { val failedOrderScript = smartTradePreconditions( - scriptGen("Order", true), - scriptGen("Order", false), - scriptGen("ExchangeTransaction", true) + scriptGen("Order", v = true), + scriptGen("Order", v = false), + scriptGen("ExchangeTransaction", v = true) ) forAll(failedOrderScript) { @@ -851,9 +851,9 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w property("ExchangeTransactions invalid if matcher script fails") { val failedMatcherScript = smartTradePreconditions( - scriptGen("Order", true), - scriptGen("Order", true), - scriptGen("ExchangeTransaction", false) + scriptGen("Order", v = true), + scriptGen("Order", v = true), + scriptGen("ExchangeTransaction", v = false) ) forAll(failedMatcherScript) { @@ -873,10 +873,10 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w val exchangeWithResignedOrder = (exchange: @unchecked) match { case e1 @ ExchangeTransaction(TxVersion.V1, bo, so, _, _, _, _, _, _, _, _) => val newSig = crypto.sign(PrivateKey(so.senderPublicKey), bo.bodyBytes()) - e1.copy(order1 = bo.copy(proofs = Proofs(Seq(newSig)))) + e1.copy(order1 = bo.withProofs(Proofs(newSig))) case e2 @ ExchangeTransaction(TxVersion.V2, bo, so, _, _, _, _, _, _, _, _) => val newSig = crypto.sign(PrivateKey(bo.senderPublicKey), so.bodyBytes()) - e2.copy(order2 = so.copy(proofs = Proofs(Seq(newSig)))) + e2.copy(order2 = so.withProofs(Proofs(newSig))) } val preconBlocks = Seq( @@ -906,9 +906,9 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w val exchangeWithResignedOrder = (exchange: @unchecked) match { case e1 @ ExchangeTransaction(TxVersion.V1, _, so, _, _, _, _, _, _, _, _) => - e1.copy(order1 = so.copy(proofs = newProofs)) + e1.copy(order1 = so.withProofs(newProofs)) case e2 @ ExchangeTransaction(TxVersion.V2, _, so, _, _, _, _, _, _, _, _) => - e2.copy(order1 = so.copy(proofs = newProofs)) + e2.copy(order1 = so.withProofs(newProofs)) } val preconBlocks = Seq( @@ -937,9 +937,9 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w genesis = GenesisTransaction.create(MATCHER.toAddress, Long.MaxValue, ts).explicitGet() tr1 = createWavesTransfer(MATCHER, buyer.toAddress, Long.MaxValue / 3, enoughFee, ts + 1).explicitGet() tr2 = createWavesTransfer(MATCHER, seller.toAddress, Long.MaxValue / 3, enoughFee, ts + 2).explicitGet() - asset1 = IssueTransaction(TxVersion.V2, buyer.publicKey, "Asset#1".utf8Bytes, Array.emptyByteArray, 1000000, 8, false, None, enoughFee, ts + 3) + asset1 = IssueTransaction(TxVersion.V2, buyer.publicKey, "Asset#1".utf8Bytes, Array.emptyByteArray, 1000000, 8, reissuable = false, None, enoughFee, ts + 3) .signWith(buyer.privateKey) - asset2 = IssueTransaction(TxVersion.V2, seller.publicKey, "Asset#2".utf8Bytes, Array.emptyByteArray, 1000000, 8, false, None, enoughFee, ts + 4) + asset2 = IssueTransaction(TxVersion.V2, seller.publicKey, "Asset#2".utf8Bytes, Array.emptyByteArray, 1000000, 8, reissuable = false, None, enoughFee, ts + 4) .signWith(seller.privateKey) setMatcherScript = SetScriptTransaction .selfSigned(1.toByte, MATCHER, Some(txScriptCompiled), enoughFee, ts + 5) @@ -983,6 +983,75 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w } } + property("Legacy price mode is only allowed in Order V4 after RideV6") { + val issue = TxHelpers.issue() + val asset = issue.asset + + def generateTx(version: TxVersion, mode: OrderPriceMode): ExchangeTransaction = { + val pair = AssetPair(asset, Waves) + + val buyOrder = + Order.buy( + version, + TxHelpers.secondSigner, + MATCHER.publicKey, + pair, + 1L, + 1_0000_0000L, + ntpTime.correctedTime(), + ntpTime.getTimestamp() + 1000, + TestValues.fee, + priceMode = mode + ) + val sellOrder = + Order.sell( + version, + TxHelpers.defaultSigner, + MATCHER.publicKey, + pair, + 1L, + 1L, + ntpTime.correctedTime(), + ntpTime.getTimestamp() + 1000, + TestValues.fee, + priceMode = mode + ) + + ExchangeTransaction + .signed( + TxVersion.V3, + MATCHER.privateKey, + buyOrder, + sellOrder, + 1L, + 1L, + TestValues.fee, + TestValues.fee, + TestValues.fee, + ntpTime.correctedTime() + ) + .explicitGet() + } + + def generateAndAppendTx(orderVersion: TxVersion, mode: OrderPriceMode, settings: WavesSettings = DomainPresets.RideV5): Unit = { + withDomain(settings) { d => + d.helpers.creditWavesToDefaultSigner() + d.helpers.creditWavesFromDefaultSigner(TxHelpers.secondAddress) + d.appendAndAssertSucceed(issue) + d.appendAndAssertSucceed(generateTx(orderVersion, mode)) + } + } + + intercept[RuntimeException](generateAndAppendTx(Order.V1, FixedDecimals)).getMessage should include("price mode should be default") + intercept[RuntimeException](generateAndAppendTx(Order.V2, FixedDecimals)).getMessage should include("price mode should be default") + intercept[RuntimeException](generateAndAppendTx(Order.V3, FixedDecimals)).getMessage should include("price mode should be default") + intercept[RuntimeException](generateAndAppendTx(Order.V4, AssetDecimals)).getMessage should include( + "Legacy price mode is only available after RideV6 activation" + ) + generateAndAppendTx(Order.V4, FixedDecimals, DomainPresets.RideV6) + generateAndAppendTx(Order.V4, AssetDecimals, DomainPresets.RideV6) + } + property("ExchangeTransaction with Orders V4 uses asset decimals for price calculation") { val enoughFee = 100000000L val buyer = accountGen.sample.get @@ -996,13 +1065,13 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w GenesisTransaction.create(MATCHER.toAddress, ENOUGH_AMT, ntpTime.getTimestamp()).explicitGet() ) val usdnTx = IssueTransaction - .selfSigned(TxVersion.V3, buyer, "USD-N", "USD-N", ENOUGH_AMT, 6.toByte, false, None, enoughFee, ntpTime.correctedTime()) + .selfSigned(TxVersion.V3, buyer, "USD-N", "USD-N", ENOUGH_AMT, 6.toByte, reissuable = false, None, enoughFee, ntpTime.correctedTime()) .explicitGet() val tidexTx = IssueTransaction - .selfSigned(TxVersion.V3, seller, "Tidex", "Tidex", ENOUGH_AMT, 2.toByte, false, None, enoughFee, ntpTime.correctedTime()) + .selfSigned(TxVersion.V3, seller, "Tidex", "Tidex", ENOUGH_AMT, 2.toByte, reissuable = false, None, enoughFee, ntpTime.correctedTime()) .explicitGet() val liquidTx = IssueTransaction - .selfSigned(TxVersion.V3, seller, "Liquid", "Liquid", ENOUGH_AMT, 8.toByte, false, None, enoughFee, ntpTime.correctedTime()) + .selfSigned(TxVersion.V3, seller, "Liquid", "Liquid", ENOUGH_AMT, 8.toByte, reissuable = false, None, enoughFee, ntpTime.correctedTime()) .explicitGet() val usdn = IssuedAsset(usdnTx.assetId) @@ -1012,12 +1081,44 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w (Seq(TestBlock.create(genesisTxs), TestBlock.create(Seq(usdnTx, tidexTx, liquidTx), Block.ProtoBlockVersion)), usdn, tidex, liquid) } - def mkExchange(txv: Byte, bov: Byte, sov: Byte, amount: Long, txPrice: Long, boPrice: Long, soPrice: Long, pair: AssetPair) - : ExchangeTransaction = { + def mkExchange( + txv: Byte, + bov: Byte, + sov: Byte, + amount: Long, + txPrice: Long, + boPrice: Long, + boMode: OrderPriceMode, + soPrice: Long, + soMode: OrderPriceMode, + pair: AssetPair + ): ExchangeTransaction = { val buyOrder = - Order.buy(bov, buyer, MATCHER.publicKey, pair, amount, boPrice, ntpTime.correctedTime(), ntpTime.getTimestamp() + 1000, enoughFee) + Order.buy( + bov, + buyer, + MATCHER.publicKey, + pair, + amount, + boPrice, + ntpTime.correctedTime(), + ntpTime.getTimestamp() + 1000, + enoughFee, + priceMode = boMode + ) val sellOrder = - Order.sell(sov, seller, MATCHER.publicKey, pair, amount, soPrice, ntpTime.correctedTime(), ntpTime.getTimestamp() + 1000, enoughFee) + Order.sell( + sov, + seller, + MATCHER.publicKey, + pair, + amount, + soPrice, + ntpTime.correctedTime(), + ntpTime.getTimestamp() + 1000, + enoughFee, + priceMode = soMode + ) ExchangeTransaction .signed(txv, MATCHER.privateKey, buyOrder, sellOrder, amount, txPrice, enoughFee, enoughFee, enoughFee, ntpTime.correctedTime()) .explicitGet() @@ -1028,49 +1129,57 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w val liquidWaves = AssetPair(liquid, Waves) val scenarios = Table( - ("transaction with orders v3", "transaction with orders v4", "transaction with orders v3 and v4", "transaction with orders v4 and v3"), ( - mkExchange(TxVersion.V2, Order.V3, Order.V3, 55768188998L, 592600L, 592600L, 592600L, wavesUsdn), - mkExchange(TxVersion.V3, Order.V4, Order.V4, 55768188998L, 59260000L, 59260000L, 59260000L, wavesUsdn), - mkExchange(TxVersion.V3, Order.V3, Order.V4, 55768188998L, 59260000L, 592600L, 59260000L, wavesUsdn), - mkExchange(TxVersion.V3, Order.V4, Order.V3, 55768188998L, 59260000L, 59260000L, 592600L, wavesUsdn) + "transaction with orders v3", + "transaction with orders v4 (v3 mode)", + "transaction with orders v4", + "transaction with orders v3 and v4 (v3 mode)", + "transaction with orders v3 and v4", + "transaction with orders v4 (v3 mode) and v3", + "transaction with orders v4 and v3" + ), + ( + mkExchange(TxVersion.V2, Order.V3, Order.V3, 55768188998L, 592600L, 592600L, DefaultPriceMode, 592600L, DefaultPriceMode, wavesUsdn), + mkExchange(TxVersion.V3, Order.V4, Order.V4, 55768188998L, 59260000L, 592600L, AssetDecimals, 592600L, AssetDecimals, wavesUsdn), + mkExchange(TxVersion.V3, Order.V4, Order.V4, 55768188998L, 59260000L, 59260000L, DefaultPriceMode, 59260000L, DefaultPriceMode, wavesUsdn), + mkExchange(TxVersion.V3, Order.V3, Order.V4, 55768188998L, 59260000L, 592600L, DefaultPriceMode, 592600L, AssetDecimals, wavesUsdn), + mkExchange(TxVersion.V3, Order.V3, Order.V4, 55768188998L, 59260000L, 592600L, DefaultPriceMode, 59260000L, FixedDecimals, wavesUsdn), + mkExchange(TxVersion.V3, Order.V4, Order.V3, 55768188998L, 59260000L, 592600L, AssetDecimals, 592600L, DefaultPriceMode, wavesUsdn), + mkExchange(TxVersion.V3, Order.V4, Order.V3, 55768188998L, 59260000L, 59260000L, FixedDecimals, 592600L, DefaultPriceMode, wavesUsdn) ), ( - mkExchange(TxVersion.V2, Order.V3, Order.V3, 213L, 35016774000000L, 35016774000000L, 35016774000000L, tidexWaves), - mkExchange(TxVersion.V3, Order.V4, Order.V4, 213L, 35016774L, 35016774L, 35016774L, tidexWaves), - mkExchange(TxVersion.V3, Order.V3, Order.V4, 213L, 35016774L, 35016774000000L, 35016774L, tidexWaves), - mkExchange(TxVersion.V3, Order.V4, Order.V3, 213L, 35016774L, 35016774L, 35016774000000L, tidexWaves) + mkExchange(TxVersion.V2, Order.V3, Order.V3, 213L, 35016774000000L, 35016774000000L, DefaultPriceMode, 35016774000000L, DefaultPriceMode, tidexWaves), + mkExchange(TxVersion.V3, Order.V4, Order.V4, 213L, 35016774L, 35016774000000L, AssetDecimals, 35016774000000L, AssetDecimals, tidexWaves), + mkExchange(TxVersion.V3, Order.V4, Order.V4, 213L, 35016774L, 35016774, FixedDecimals, 35016774L, FixedDecimals, tidexWaves), + mkExchange(TxVersion.V3, Order.V3, Order.V4, 213L, 35016774L, 35016774000000L, DefaultPriceMode, 35016774000000L, AssetDecimals, tidexWaves), + mkExchange(TxVersion.V3, Order.V3, Order.V4, 213L, 35016774L, 35016774000000L, DefaultPriceMode, 35016774L, FixedDecimals, tidexWaves), + mkExchange(TxVersion.V3, Order.V4, Order.V3, 213L, 35016774L, 35016774000000L, AssetDecimals, 35016774000000L, DefaultPriceMode, tidexWaves), + mkExchange(TxVersion.V3, Order.V4, Order.V3, 213L, 35016774L, 35016774L, FixedDecimals, 35016774000000L, DefaultPriceMode, tidexWaves) ), ( - mkExchange(TxVersion.V2, Order.V3, Order.V3, 2000000000L, 13898832L, 13898832L, 13898832L, liquidWaves), - mkExchange(TxVersion.V3, Order.V4, Order.V4, 2000000000L, 13898832L, 13898832L, 13898832L, liquidWaves), - mkExchange(TxVersion.V3, Order.V3, Order.V4, 2000000000L, 13898832L, 13898832L, 13898832L, liquidWaves), - mkExchange(TxVersion.V3, Order.V4, Order.V3, 2000000000L, 13898832L, 13898832L, 13898832L, liquidWaves) + mkExchange(TxVersion.V2, Order.V3, Order.V3, 2000000000L, 13898832L, 13898832L, DefaultPriceMode, 13898832L, DefaultPriceMode, liquidWaves), + mkExchange(TxVersion.V3, Order.V4, Order.V4, 2000000000L, 13898832L, 13898832L, AssetDecimals, 13898832L, AssetDecimals, liquidWaves), + mkExchange(TxVersion.V3, Order.V4, Order.V4, 2000000000L, 13898832L, 13898832L, FixedDecimals, 13898832L, FixedDecimals, liquidWaves), + mkExchange(TxVersion.V3, Order.V3, Order.V4, 2000000000L, 13898832L, 13898832L, DefaultPriceMode, 13898832L, AssetDecimals, liquidWaves), + mkExchange(TxVersion.V3, Order.V3, Order.V4, 2000000000L, 13898832L, 13898832L, DefaultPriceMode, 13898832L, FixedDecimals, liquidWaves), + mkExchange(TxVersion.V3, Order.V4, Order.V3, 2000000000L, 13898832L, 13898832L, AssetDecimals, 13898832L, DefaultPriceMode, liquidWaves), + mkExchange(TxVersion.V3, Order.V4, Order.V3, 2000000000L, 13898832L, 13898832L, FixedDecimals, 13898832L, DefaultPriceMode, liquidWaves) ) ) forAll(scenarios) { - case (txWithV3, txWithV4, txWithV3V4, txWithV4V3) => + case (txWithV3, txWithV4AsV3, txWithV4, txWithV3V4AsV3, txWithV3V4, txWithV4AsV3V3, txWithV4V3) => val portfolios = collection.mutable.ListBuffer[Map[Address, Portfolio]]() - assertDiffAndState(preconditions, TestBlock.create(Seq(txWithV4), Block.ProtoBlockVersion), fsWithBlockV5) { - case (blockDiff, _) => portfolios += blockDiff.portfolios - } - - assertDiffAndState(preconditions, TestBlock.create(Seq(txWithV3V4), Block.ProtoBlockVersion), fsWithBlockV5) { - case (blockDiff, _) => portfolios += blockDiff.portfolios - } - - assertDiffAndState(preconditions, TestBlock.create(Seq(txWithV4V3), Block.ProtoBlockVersion), fsWithBlockV5) { - case (blockDiff, _) => portfolios += blockDiff.portfolios - } - - assertDiffAndState(preconditions, TestBlock.create(Seq(txWithV3), Block.ProtoBlockVersion), fsWithBlockV5) { - case (blockDiff, _) => portfolios += blockDiff.portfolios - } + Seq(txWithV3, txWithV4AsV3, txWithV4, txWithV3V4AsV3, txWithV3V4, txWithV4AsV3V3, txWithV4V3) + .foreach { tx => + assertDiffAndState(preconditions, TestBlock.create(Seq(tx), Block.ProtoBlockVersion), DomainPresets.RideV6.blockchainSettings.functionalitySettings) { + case (blockDiff, _) => portfolios += blockDiff.portfolios + } + } // all portfolios built on the state and on the composite blockchain are equal - portfolios.forall(_ == portfolios.head) shouldBe true + portfolios.distinct.size shouldBe 1 } } @@ -1438,9 +1547,9 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w genesis = GenesisTransaction.create(MATCHER.toAddress, Long.MaxValue, ts).explicitGet() tr1 = createWavesTransfer(MATCHER, buyer.toAddress, Long.MaxValue / 3, enoughFee, ts + 1).explicitGet() tr2 = createWavesTransfer(MATCHER, seller.toAddress, Long.MaxValue / 3, enoughFee, ts + 2).explicitGet() - asset1 = IssueTransaction(TxVersion.V2, buyer.publicKey, "Asset#1".utf8Bytes, Array.emptyByteArray, 1000000, 8, false, None, enoughFee, ts + 3) + asset1 = IssueTransaction(TxVersion.V2, buyer.publicKey, "Asset#1".utf8Bytes, Array.emptyByteArray, 1000000, 8, reissuable = false, None, enoughFee, ts + 3) .signWith(buyer.privateKey) - asset2 = IssueTransaction(TxVersion.V2, seller.publicKey, "Asset#2".utf8Bytes, Array.emptyByteArray, 1000000, 8, false, None, enoughFee, ts + 4) + asset2 = IssueTransaction(TxVersion.V2, seller.publicKey, "Asset#2".utf8Bytes, Array.emptyByteArray, 1000000, 8, reissuable = false, None, enoughFee, ts + 4) .signWith(seller.privateKey) setMatcherScript = SetScriptTransaction .selfSigned(1.toByte, MATCHER, Some(txScriptCompiled), enoughFee, ts + 5) diff --git a/node/src/test/scala/com/wavesplatform/transaction/ExchangeTransactionSpecification.scala b/node/src/test/scala/com/wavesplatform/transaction/ExchangeTransactionSpecification.scala index 2e5905e4366..607ced30519 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/ExchangeTransactionSpecification.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/ExchangeTransactionSpecification.scala @@ -8,15 +8,17 @@ import com.wavesplatform.test.PropSpec import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.{GenericError, OrderValidationError} import com.wavesplatform.transaction.assets.exchange.AssetPair.extractAssetId -import com.wavesplatform.transaction.assets.exchange.{Order, _} +import com.wavesplatform.transaction.assets.exchange.{Order, *} import com.wavesplatform.transaction.serialization.impl.ExchangeTxSerializer -import com.wavesplatform.{NTPTime, crypto} +import com.wavesplatform.{crypto, NTPTime} import org.scalacheck.Gen import play.api.libs.json.Json - import scala.math.pow -class ExchangeTransactionSpecification extends PropSpec with NTPTime { +import com.wavesplatform.utils.JsonMatchers + +//noinspection ScalaStyle +class ExchangeTransactionSpecification extends PropSpec with NTPTime with JsonMatchers { val versionsGen: Gen[(Byte, Byte, Byte)] = Gen.oneOf( (1.toByte, 1.toByte, 1.toByte), (1.toByte, 2.toByte, 2.toByte), @@ -111,7 +113,7 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { ) val tx = ExchangeTxSerializer.parseBytes(bytes).get - tx.json() shouldBe json + tx.json() should matchJson(json) assert(crypto.verify(tx.sellOrder.signature, tx.sellOrder.bodyBytes(), tx.sellOrder.sender), "sellOrder signature should be valid") assert(crypto.verify(tx.buyOrder.signature, tx.buyOrder.bodyBytes(), tx.buyOrder.sender), "buyOrder signature should be valid") assert(crypto.verify(tx.signature, tx.bodyBytes(), tx.sender), "signature should be valid") @@ -189,130 +191,129 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { property("ExchangeTransaction invariants validation") { - forAll(preconditions) { - case (sender1, sender2, matcher, pair, buyerMatcherFeeAssetId, sellerMatcherFeeAssetId, versions) => - val time = ntpTime.correctedTime() - val expirationTimestamp = time + Order.MaxLiveTime - - val buyPrice = 60 * Order.PriceConstant - val sellPrice = 50 * Order.PriceConstant - val buyAmount = 2 - val sellAmount = 3 - val buyMatcherFee = 1 - val sellMatcherFee = 2 - - val (buyV, sellV, exchangeV) = versions - - val buy = Order.buy( - buyV, - sender1, - matcher.publicKey, - pair, - buyAmount, - buyPrice, - time, - expirationTimestamp, - buyMatcherFee, - if (buyV == 3) buyerMatcherFeeAssetId else Waves - ) - val sell = Order.sell( - sellV, - sender2, - matcher.publicKey, - pair, - sellAmount, - sellPrice, - time, - expirationTimestamp, - sellMatcherFee, - if (sellV == 3) sellerMatcherFeeAssetId else Waves - ) + forAll(preconditions) { case (sender1, sender2, matcher, pair, buyerMatcherFeeAssetId, sellerMatcherFeeAssetId, versions) => + val time = ntpTime.correctedTime() + val expirationTimestamp = time + Order.MaxLiveTime + + val buyPrice = 60 * Order.PriceConstant + val sellPrice = 50 * Order.PriceConstant + val buyAmount = 2 + val sellAmount = 3 + val buyMatcherFee = 1 + val sellMatcherFee = 2 + + val (buyV, sellV, exchangeV) = versions + + val buy = Order.buy( + buyV, + sender1, + matcher.publicKey, + pair, + buyAmount, + buyPrice, + time, + expirationTimestamp, + buyMatcherFee, + if (buyV == 3) buyerMatcherFeeAssetId else Waves + ) + val sell = Order.sell( + sellV, + sender2, + matcher.publicKey, + pair, + sellAmount, + sellPrice, + time, + expirationTimestamp, + sellMatcherFee, + if (sellV == 3) sellerMatcherFeeAssetId else Waves + ) - def create( - matcher: KeyPair = sender1, - buyOrder: Order = buy, - sellOrder: Order = sell, - amount: Long = buyAmount, - price: Long = sellPrice, - buyMatcherFee: Long = buyMatcherFee, - sellMatcherFee: Long = 1, - fee: Long = 1, - timestamp: Long = expirationTimestamp - Order.MaxLiveTime, - version: Byte = exchangeV - ): Either[ValidationError, ExchangeTransaction] = { - if (version == 1) { - ExchangeTransaction.signed( - 1.toByte, - matcher = sender1.privateKey, - order1 = buyOrder, - order2 = sellOrder, - amount = amount, - price = price, - buyMatcherFee = buyMatcherFee, - sellMatcherFee = sellMatcherFee, - fee = fee, - timestamp = timestamp - ) - } else { - ExchangeTransaction.signed( - version, - matcher = sender1.privateKey, - order1 = buyOrder, - order2 = sellOrder, - amount = amount, - price = price, - buyMatcherFee = buyMatcherFee, - sellMatcherFee = sellMatcherFee, - fee = fee, - timestamp = timestamp - ) - } + def create( + matcher: KeyPair = sender1, + buyOrder: Order = buy, + sellOrder: Order = sell, + amount: Long = buyAmount, + price: Long = sellPrice, + buyMatcherFee: Long = buyMatcherFee, + sellMatcherFee: Long = 1, + fee: Long = 1, + timestamp: Long = expirationTimestamp - Order.MaxLiveTime, + version: Byte = exchangeV + ): Either[ValidationError, ExchangeTransaction] = { + if (version == 1) { + ExchangeTransaction.signed( + 1.toByte, + matcher = matcher.privateKey, + order1 = buyOrder, + order2 = sellOrder, + amount = amount, + price = price, + buyMatcherFee = buyMatcherFee, + sellMatcherFee = sellMatcherFee, + fee = fee, + timestamp = timestamp + ) + } else { + ExchangeTransaction.signed( + version, + matcher = matcher.privateKey, + order1 = buyOrder, + order2 = sellOrder, + amount = amount, + price = price, + buyMatcherFee = buyMatcherFee, + sellMatcherFee = sellMatcherFee, + fee = fee, + timestamp = timestamp + ) } + } - buy.version shouldBe buyV - sell.version shouldBe sellV - - create() shouldBe an[Right[_, _]] - create(fee = pow(10, 18).toLong) shouldBe an[Right[_, _]] - create(amount = Order.MaxAmount) shouldBe an[Right[_, _]] - - create(fee = -1) shouldBe an[Left[_, _]] - create(amount = -1) shouldBe an[Left[_, _]] - create(amount = Order.MaxAmount + 1) shouldBe an[Left[_, _]] - create(price = -1) shouldBe an[Left[_, _]] - create(sellMatcherFee = Order.MaxAmount + 1) shouldBe an[Left[_, _]] - create(buyMatcherFee = Order.MaxAmount + 1) shouldBe an[Left[_, _]] - create(fee = Order.MaxAmount + 1) shouldBe an[Left[_, _]] - - create(buyOrder = buy.copy(orderType = OrderType.SELL)) shouldBe Left(GenericError("order1 should have OrderType.BUY")) - create(buyOrder = buy.copy(amount = 0)) shouldBe an[Left[_, _]] - create(buyOrder = buy.copy(amount = -1)) shouldBe an[Left[_, _]] - create(buyOrder = buy.copy(amount = Order.MaxAmount + 1)) shouldBe an[Left[_, _]] - create(buyOrder = buy.copy(assetPair = buy.assetPair.copy(amountAsset = sell.assetPair.priceAsset))) shouldBe an[Left[_, _]] - create(buyOrder = buy.copy(expiration = 1L)) shouldBe an[Left[_, _]] - create(buyOrder = buy.copy(expiration = buy.expiration + 1)) shouldBe an[Left[_, _]] - create(buyOrder = buy.copy(price = -1)) shouldBe an[Left[_, _]] - create(buyOrder = buy.copy(matcherPublicKey = sender2.publicKey)) shouldBe an[Left[_, _]] - - create(sellOrder = sell.copy(orderType = OrderType.BUY)) shouldBe Left(GenericError("sellOrder should has OrderType.SELL")) - create(sellOrder = sell.copy(amount = 0)) shouldBe an[Left[_, _]] - create(sellOrder = sell.copy(amount = -1)) shouldBe an[Left[_, _]] - create(sellOrder = sell.copy(amount = Order.MaxAmount + 1)) shouldBe an[Left[_, _]] - create(sellOrder = sell.copy(assetPair = sell.assetPair.copy(priceAsset = buy.assetPair.amountAsset))) shouldBe an[Left[_, _]] - create(sellOrder = sell.copy(expiration = 1L)) shouldBe an[Left[_, _]] - create(sellOrder = sell.copy(expiration = sell.expiration + 1)) shouldBe an[Left[_, _]] - create(sellOrder = sell.copy(price = -1)) shouldBe an[Left[_, _]] - create(sellOrder = sell.copy(matcherPublicKey = sender2.publicKey)) shouldBe an[Left[_, _]] - - create(sellOrder = buy, buyOrder = sell) shouldBe Left(GenericError("order1 should have OrderType.BUY")) - create(version = TxVersion.V3, sellOrder = buy, buyOrder = sell) shouldBe an[Right[_, _]] - create(version = TxVersion.V3, sellOrder = sell, buyOrder = sell) shouldBe Left(GenericError("buyOrder should has OrderType.BUY")) - create(version = TxVersion.V3, sellOrder = buy, buyOrder = buy) shouldBe Left(GenericError("sellOrder should has OrderType.SELL")) - - create( - buyOrder = buy.copy(assetPair = buy.assetPair.copy(amountAsset = Waves)), - sellOrder = sell.copy(assetPair = sell.assetPair.copy(priceAsset = IssuedAsset(ByteStr(Array(1: Byte))))) - ) shouldBe an[Left[_, _]] + buy.version shouldBe buyV + sell.version shouldBe sellV + + create() shouldBe an[Right[_, _]] + create(fee = pow(10, 18).toLong) shouldBe an[Right[_, _]] + create(amount = Order.MaxAmount) shouldBe an[Right[_, _]] + + create(fee = -1) shouldBe an[Left[_, _]] + create(amount = -1) shouldBe an[Left[_, _]] + create(amount = Order.MaxAmount + 1) shouldBe an[Left[_, _]] + create(price = -1) shouldBe an[Left[_, _]] + create(sellMatcherFee = Order.MaxAmount + 1) shouldBe an[Left[_, _]] + create(buyMatcherFee = Order.MaxAmount + 1) shouldBe an[Left[_, _]] + create(fee = Order.MaxAmount + 1) shouldBe an[Left[_, _]] + + create(buyOrder = buy.copy(orderType = OrderType.SELL)) shouldBe Left(GenericError("order1 should have OrderType.BUY")) + create(buyOrder = buy.copy(amount = 0)) shouldBe an[Left[_, _]] + create(buyOrder = buy.copy(amount = -1)) shouldBe an[Left[_, _]] + create(buyOrder = buy.copy(amount = Order.MaxAmount + 1)) shouldBe an[Left[_, _]] + create(buyOrder = buy.copy(assetPair = buy.assetPair.copy(amountAsset = sell.assetPair.priceAsset))) shouldBe an[Left[_, _]] + create(buyOrder = buy.copy(expiration = 1L)) shouldBe an[Left[_, _]] + create(buyOrder = buy.copy(expiration = buy.expiration + 1)) shouldBe an[Left[_, _]] + create(buyOrder = buy.copy(price = -1)) shouldBe an[Left[_, _]] + create(buyOrder = buy.copy(matcherPublicKey = sender2.publicKey)) shouldBe an[Left[_, _]] + + create(sellOrder = sell.copy(orderType = OrderType.BUY)) shouldBe Left(GenericError("sellOrder should has OrderType.SELL")) + create(sellOrder = sell.copy(amount = 0)) shouldBe an[Left[_, _]] + create(sellOrder = sell.copy(amount = -1)) shouldBe an[Left[_, _]] + create(sellOrder = sell.copy(amount = Order.MaxAmount + 1)) shouldBe an[Left[_, _]] + create(sellOrder = sell.copy(assetPair = sell.assetPair.copy(priceAsset = buy.assetPair.amountAsset))) shouldBe an[Left[_, _]] + create(sellOrder = sell.copy(expiration = 1L)) shouldBe an[Left[_, _]] + create(sellOrder = sell.copy(expiration = sell.expiration + 1)) shouldBe an[Left[_, _]] + create(sellOrder = sell.copy(price = -1)) shouldBe an[Left[_, _]] + create(sellOrder = sell.copy(matcherPublicKey = sender2.publicKey)) shouldBe an[Left[_, _]] + + create(sellOrder = buy, buyOrder = sell) shouldBe Left(GenericError("order1 should have OrderType.BUY")) + create(version = TxVersion.V3, sellOrder = buy, buyOrder = sell) shouldBe an[Right[_, _]] + create(version = TxVersion.V3, sellOrder = sell, buyOrder = sell) shouldBe Left(GenericError("buyOrder should has OrderType.BUY")) + create(version = TxVersion.V3, sellOrder = buy, buyOrder = buy) shouldBe Left(GenericError("sellOrder should has OrderType.SELL")) + + create( + buyOrder = buy.copy(assetPair = buy.assetPair.copy(amountAsset = Waves)), + sellOrder = sell.copy(assetPair = sell.assetPair.copy(priceAsset = IssuedAsset(ByteStr(Array(1: Byte))))) + ) shouldBe an[Left[_, _]] } } @@ -351,50 +352,49 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { property("Test transaction with small amount and expired order") { - forAll(preconditions) { - case (sender1, sender2, matcher, pair, buyerMatcherFeeAssetId, sellerMatcherFeeAssetId, versions) => - val time = ntpTime.correctedTime() - val expirationTimestamp = time + Order.MaxLiveTime - val buyPrice = 1 * Order.PriceConstant - val sellPrice = (0.50 * Order.PriceConstant).toLong - val matcherFee = 300000L - val (sellV, buyV, exchangeV) = versions - - val sell = - Order.sell( - sellV, - sender2, - matcher.publicKey, - pair, - 2, - sellPrice, - time, - expirationTimestamp, - matcherFee, - if (sellV == 3) sellerMatcherFeeAssetId else Waves - ) - val buy = - Order.buy( - buyV, - sender1, - matcher.publicKey, - pair, - 1, - buyPrice, - time, - expirationTimestamp, - matcherFee, - if (buyV == 3) buyerMatcherFeeAssetId else Waves - ) + forAll(preconditions) { case (sender1, sender2, matcher, pair, buyerMatcherFeeAssetId, sellerMatcherFeeAssetId, versions) => + val time = ntpTime.correctedTime() + val expirationTimestamp = time + Order.MaxLiveTime + val buyPrice = 1 * Order.PriceConstant + val sellPrice = (0.50 * Order.PriceConstant).toLong + val matcherFee = 300000L + val (sellV, buyV, exchangeV) = versions - createExTx(buy, sell, sellPrice, matcher, exchangeV) shouldBe an[Right[_, _]] + val sell = + Order.sell( + sellV, + sender2, + matcher.publicKey, + pair, + 2, + sellPrice, + time, + expirationTimestamp, + matcherFee, + if (sellV == 3) sellerMatcherFeeAssetId else Waves + ) + val buy = + Order.buy( + buyV, + sender1, + matcher.publicKey, + pair, + 1, + buyPrice, + time, + expirationTimestamp, + matcherFee, + if (buyV == 3) buyerMatcherFeeAssetId else Waves + ) - val sell1 = - if (sellV == 3) { - Order.sell(sellV, sender2, matcher.publicKey, pair, 1, buyPrice, time, time - 1, matcherFee, sellerMatcherFeeAssetId) - } else Order.sell(sellV, sender2, matcher.publicKey, pair, 1, buyPrice, time, time - 1, matcherFee) + createExTx(buy, sell, sellPrice, matcher, exchangeV) shouldBe an[Right[_, _]] - createExTx(buy, sell1, buyPrice, matcher, exchangeV) shouldBe Left(OrderValidationError(sell1, "expiration should be > currentTime")) + val sell1 = + if (sellV == 3) { + Order.sell(sellV, sender2, matcher.publicKey, pair, 1, buyPrice, time, time - 1, matcherFee, sellerMatcherFeeAssetId) + } else Order.sell(sellV, sender2, matcher.publicKey, pair, 1, buyPrice, time, time - 1, matcherFee) + + createExTx(buy, sell1, buyPrice, matcher, exchangeV) shouldBe Left(OrderValidationError(sell1, "expiration should be > currentTime")) } } @@ -451,7 +451,10 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { val buy = Order( Order.V1, - PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet(), + OrderAuthentication.OrderProofs( + PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet(), + Proofs(ByteStr.decodeBase58("2bkuGwECMFGyFqgoHV4q7GRRWBqYmBFWpYRkzgYANR4nN2twgrNaouRiZBqiK2RJzuo9NooB9iRiuZ4hypBbUQs").get) + ), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.BUY, @@ -459,13 +462,15 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { 6000000000L, 1526992336241L, 1529584336241L, - 1, - proofs = Proofs(ByteStr.decodeBase58("2bkuGwECMFGyFqgoHV4q7GRRWBqYmBFWpYRkzgYANR4nN2twgrNaouRiZBqiK2RJzuo9NooB9iRiuZ4hypBbUQs").get) + 1 ) val sell = Order( Order.V1, - PublicKey.fromBase58String("7E9Za8v8aT6EyU1sX91CVK7tWUeAetnNYDxzKZsyjyKV").explicitGet(), + OrderAuthentication.OrderProofs( + PublicKey.fromBase58String("7E9Za8v8aT6EyU1sX91CVK7tWUeAetnNYDxzKZsyjyKV").explicitGet(), + Proofs(ByteStr.decodeBase58("2R6JfmNjEnbXAA6nt8YuCzSf1effDS4Wkz8owpCD9BdCNn864SnambTuwgLRYzzeP5CAsKHEviYKAJ2157vdr5Zq").get) + ), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.SELL, @@ -473,8 +478,7 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { 5000000000L, 1526992336241L, 1529584336241L, - 2, - proofs = Proofs(ByteStr.decodeBase58("2R6JfmNjEnbXAA6nt8YuCzSf1effDS4Wkz8owpCD9BdCNn864SnambTuwgLRYzzeP5CAsKHEviYKAJ2157vdr5Zq").get) + 2 ) val tx = ExchangeTransaction @@ -492,7 +496,7 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { ) .explicitGet() - js shouldEqual tx.json() + js should matchJson(tx.json()) } property("JSON format validation V2") { @@ -547,7 +551,10 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { val buy = Order( Order.V2, - PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet(), + OrderAuthentication.OrderProofs( + PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet(), + Proofs(ByteStr.decodeBase58("2bkuGwECMFGyFqgoHV4q7GRRWBqYmBFWpYRkzgYANR4nN2twgrNaouRiZBqiK2RJzuo9NooB9iRiuZ4hypBbUQs").get) + ), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.BUY, @@ -555,13 +562,15 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { 6000000000L, 1526992336241L, 1529584336241L, - 1, - proofs = Proofs(Seq(ByteStr.decodeBase58("2bkuGwECMFGyFqgoHV4q7GRRWBqYmBFWpYRkzgYANR4nN2twgrNaouRiZBqiK2RJzuo9NooB9iRiuZ4hypBbUQs").get)) + 1 ) val sell = Order( Order.V1, - PublicKey.fromBase58String("7E9Za8v8aT6EyU1sX91CVK7tWUeAetnNYDxzKZsyjyKV").explicitGet(), + OrderAuthentication.OrderProofs( + PublicKey.fromBase58String("7E9Za8v8aT6EyU1sX91CVK7tWUeAetnNYDxzKZsyjyKV").explicitGet(), + Proofs(ByteStr.decodeBase58("2R6JfmNjEnbXAA6nt8YuCzSf1effDS4Wkz8owpCD9BdCNn864SnambTuwgLRYzzeP5CAsKHEviYKAJ2157vdr5Zq").get) + ), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.SELL, @@ -569,8 +578,7 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { 5000000000L, 1526992336241L, 1529584336241L, - 2, - proofs = Proofs(ByteStr.decodeBase58("2R6JfmNjEnbXAA6nt8YuCzSf1effDS4Wkz8owpCD9BdCNn864SnambTuwgLRYzzeP5CAsKHEviYKAJ2157vdr5Zq").get) + 2 ) val tx = ExchangeTransaction @@ -588,7 +596,7 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { ) .explicitGet() - js shouldEqual tx.json() + js should matchJson(tx.json()) } property("JSON format validation V2 OrderV3") { @@ -644,7 +652,10 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { val buy = Order( Order.V3, - PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet(), + OrderAuthentication.OrderProofs( + PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet(), + Proofs(ByteStr.decodeBase58("2bkuGwECMFGyFqgoHV4q7GRRWBqYmBFWpYRkzgYANR4nN2twgrNaouRiZBqiK2RJzuo9NooB9iRiuZ4hypBbUQs").get) + ), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.BUY, @@ -653,13 +664,15 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { 1526992336241L, 1529584336241L, 1, - extractAssetId("9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, - Proofs(ByteStr.decodeBase58("2bkuGwECMFGyFqgoHV4q7GRRWBqYmBFWpYRkzgYANR4nN2twgrNaouRiZBqiK2RJzuo9NooB9iRiuZ4hypBbUQs").get) + extractAssetId("9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get ) val sell = Order( Order.V1, - PublicKey.fromBase58String("7E9Za8v8aT6EyU1sX91CVK7tWUeAetnNYDxzKZsyjyKV").explicitGet(), + OrderAuthentication.OrderProofs( + PublicKey.fromBase58String("7E9Za8v8aT6EyU1sX91CVK7tWUeAetnNYDxzKZsyjyKV").explicitGet(), + Proofs(ByteStr.decodeBase58("2R6JfmNjEnbXAA6nt8YuCzSf1effDS4Wkz8owpCD9BdCNn864SnambTuwgLRYzzeP5CAsKHEviYKAJ2157vdr5Zq").get) + ), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.SELL, @@ -667,8 +680,7 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { 5000000000L, 1526992336241L, 1529584336241L, - 2, - proofs = Proofs(ByteStr.decodeBase58("2R6JfmNjEnbXAA6nt8YuCzSf1effDS4Wkz8owpCD9BdCNn864SnambTuwgLRYzzeP5CAsKHEviYKAJ2157vdr5Zq").get) + 2 ) val tx = ExchangeTransaction @@ -686,6 +698,6 @@ class ExchangeTransactionSpecification extends PropSpec with NTPTime { ) .explicitGet() - js shouldEqual tx.json() + js should matchJson(tx.json()) } } diff --git a/node/src/test/scala/com/wavesplatform/transaction/OrderSpecification.scala b/node/src/test/scala/com/wavesplatform/transaction/OrderSpecification.scala index 9911edfae93..8838c6204d2 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/OrderSpecification.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/OrderSpecification.scala @@ -2,12 +2,11 @@ package com.wavesplatform.transaction import com.wavesplatform.NTPTime import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.test._ +import com.wavesplatform.test.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} -import com.wavesplatform.transaction.assets.exchange.{AssetPair, Order, OrderType} +import com.wavesplatform.transaction.assets.exchange.{AssetPair, Order, OrderAuthentication, OrderType} import com.wavesplatform.transaction.smart.Verifier -import org.scalatest._ - +import org.scalatest.* import scala.util.Random class OrderSpecification extends PropSpec with ValidationMatcher with NTPTime { @@ -77,64 +76,68 @@ class OrderSpecification extends PropSpec with ValidationMatcher with NTPTime { property("Order signature validation") { val err = "Proof doesn't validate as signature" - forAll(orderGen, accountGen) { - case (order, pka) => - val rndAsset = Array[Byte](32) - - Random.nextBytes(rndAsset) - - Verifier.verifyAsEllipticCurveSignature(order, checkWeakPk = false) shouldBe an[Right[_, _]] - Verifier.verifyAsEllipticCurveSignature(order.copy(senderPublicKey = pka.publicKey), checkWeakPk = false) should produce(err) - Verifier.verifyAsEllipticCurveSignature(order.copy(matcherPublicKey = pka.publicKey), checkWeakPk = false) should produce(err) - val assetPair = order.assetPair - Verifier.verifyAsEllipticCurveSignature( - order - .copy(assetPair = assetPair.copy(amountAsset = IssuedAsset(ByteStr(rndAsset)))), - checkWeakPk = false - ) should produce(err) - Verifier.verifyAsEllipticCurveSignature( - order - .copy(assetPair = assetPair.copy(priceAsset = IssuedAsset(ByteStr(rndAsset)))), - checkWeakPk = false - ) should produce(err) - Verifier.verifyAsEllipticCurveSignature(order.copy(orderType = OrderType.reverse(order.orderType)), checkWeakPk = false) should produce(err) - Verifier.verifyAsEllipticCurveSignature(order.copy(price = order.price + 1), checkWeakPk = false) should produce(err) - Verifier.verifyAsEllipticCurveSignature(order.copy(amount = order.amount + 1), checkWeakPk = false) should produce(err) - Verifier.verifyAsEllipticCurveSignature(order.copy(expiration = order.expiration + 1), checkWeakPk = false) should produce(err) - Verifier.verifyAsEllipticCurveSignature(order.copy(matcherFee = order.matcherFee + 1), checkWeakPk = false) should produce(err) - Verifier.verifyAsEllipticCurveSignature(order.copy(proofs = Proofs(Seq(ByteStr(pka.publicKey.arr ++ pka.publicKey.arr)))), checkWeakPk = false) should produce(err) + forAll(orderGen, accountGen) { case (order, pka) => + val rndAsset = Array[Byte](32) + + Random.nextBytes(rndAsset) + + Verifier.verifyAsEllipticCurveSignature(order, checkWeakPk = false) shouldBe an[Right[?, ?]] + Verifier.verifyAsEllipticCurveSignature( + order.copy(orderAuthentication = OrderAuthentication.OrderProofs(pka.publicKey, Proofs(ByteStr.empty))), + checkWeakPk = false + ) should produce(err) + Verifier.verifyAsEllipticCurveSignature(order.copy(matcherPublicKey = pka.publicKey), checkWeakPk = false) should produce(err) + val assetPair = order.assetPair + Verifier.verifyAsEllipticCurveSignature( + order + .copy(assetPair = assetPair.copy(amountAsset = IssuedAsset(ByteStr(rndAsset)))), + checkWeakPk = false + ) should produce(err) + Verifier.verifyAsEllipticCurveSignature( + order + .copy(assetPair = assetPair.copy(priceAsset = IssuedAsset(ByteStr(rndAsset)))), + checkWeakPk = false + ) should produce(err) + Verifier.verifyAsEllipticCurveSignature(order.copy(orderType = OrderType.reverse(order.orderType)), checkWeakPk = false) should produce(err) + Verifier.verifyAsEllipticCurveSignature(order.copy(price = order.price + 1), checkWeakPk = false) should produce(err) + Verifier.verifyAsEllipticCurveSignature(order.copy(amount = order.amount + 1), checkWeakPk = false) should produce(err) + Verifier.verifyAsEllipticCurveSignature(order.copy(expiration = order.expiration + 1), checkWeakPk = false) should produce(err) + Verifier.verifyAsEllipticCurveSignature(order.copy(matcherFee = order.matcherFee + 1), checkWeakPk = false) should produce(err) + Verifier.verifyAsEllipticCurveSignature( + order.withProofs(Proofs(Seq(ByteStr(pka.publicKey.arr ++ pka.publicKey.arr)))), + checkWeakPk = false + ) should produce(err) } } property("Buy and Sell orders") { - forAll(orderParamGen) { - case (sender, matcher, pair, _, amount, price, timestamp, _, _) => - val expiration = timestamp + Order.MaxLiveTime - 1000 - val buy = Order.buy( - Order.V1, - sender = sender, - matcher = matcher.publicKey, - pair = pair, - amount = amount, - price = price, - timestamp = timestamp, - expiration = expiration, - matcherFee = price - ) - buy.orderType shouldBe OrderType.BUY - - val sell = Order.sell( - Order.V1, - sender = sender, - matcher = matcher.publicKey, - pair = pair, - amount = amount, - price = price, - timestamp = timestamp, - expiration = expiration, - matcherFee = price - ) - sell.orderType shouldBe OrderType.SELL + forAll(orderParamGen) { case (sender, matcher, pair, _, amount, price, timestamp, _, _) => + val expiration = timestamp + Order.MaxLiveTime - 1000 + val buy = Order.buy( + Order.V1, + sender = sender, + matcher = matcher.publicKey, + pair = pair, + amount = amount, + price = price, + timestamp = timestamp, + expiration = expiration, + matcherFee = price + ) + buy.orderType shouldBe OrderType.BUY + + val sell = Order.sell( + Order.V1, + sender = sender, + matcher = matcher.publicKey, + pair = pair, + amount = amount, + price = price, + timestamp = timestamp, + expiration = expiration, + matcherFee = price + ) + sell.orderType shouldBe OrderType.SELL } } diff --git a/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala b/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala index 77a81f017b1..4461d356c91 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala @@ -44,6 +44,10 @@ object TxHelpers { lastTimestamp } + @throws[IllegalArgumentException] + def signature(sig: String): Proofs = + Proofs(ByteStr.decodeBase58(sig).get) + def genesis(address: Address, amount: Long = 100000000.waves): GenesisTransaction = GenesisTransaction.create(address, amount, timestamp).explicitGet() diff --git a/node/src/test/scala/com/wavesplatform/transaction/assets/exchange/EthOrderSpec.scala b/node/src/test/scala/com/wavesplatform/transaction/assets/exchange/EthOrderSpec.scala index 0749dc2c623..8f37b858751 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/assets/exchange/EthOrderSpec.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/assets/exchange/EthOrderSpec.scala @@ -5,61 +5,29 @@ import com.wavesplatform.common.state.ByteStr import com.wavesplatform.test.{FlatSpec, TestTime} import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.BlockchainStubHelpers -import com.wavesplatform.common.utils._ +import com.wavesplatform.common.utils.* import com.wavesplatform.state.diffs.TransactionDiffer -import com.wavesplatform.transaction.{Proofs, TxHelpers, TxVersion} -import com.wavesplatform.utils.{DiffMatchers, EthEncoding, EthHelpers, EthSetChainId} +import com.wavesplatform.transaction.{TxHelpers, TxVersion} +import com.wavesplatform.utils.{DiffMatchers, EthEncoding, EthHelpers, EthSetChainId, JsonMatchers} import org.scalamock.scalatest.PathMockFactory import org.scalatest.BeforeAndAfterAll +import play.api.libs.json.{JsObject, Json} class EthOrderSpec extends FlatSpec with BeforeAndAfterAll with PathMockFactory with BlockchainStubHelpers - with EthHelpers with EthSetChainId - with DiffMatchers { - - val ethBuyOrder = Order( - Order.V4, - TestEthOrdersPublicKey, - TxHelpers.matcher.publicKey, - AssetPair(IssuedAsset(ByteStr(EthStubBytes32)), Waves), - OrderType.BUY, - 1, - 100L, - 1, - 123, - 100000, - Waves, - eip712Signature = EthSignature( - "0xe5ff562bfb0296e95b631365599c87f1c5002597bf56a131f289765275d2580f5344c62999404c37cd858ea037328ac91eca16ad1ce69c345ebb52fde70b66251c" - ) - ) - - val ethSellOrder = Order( - Order.V4, - TestEthOrdersPublicKey, - TxHelpers.matcher.publicKey, - AssetPair(IssuedAsset(ByteStr(EthStubBytes32)), Waves), - OrderType.SELL, - 1, - 100L, - 1, - 123, - 100000, - Waves, - eip712Signature = EthSignature( - "0xc8ba2bdafd27742546b3be34883efc51d6cdffbb235798d7b51876c6854791f019b0522d7a39b6f2087cba46ae86919b71a2d9d7920dfc8e00246d8f02a258f21b" - ) - ) + with DiffMatchers + with JsonMatchers { + import EthOrderSpec.{ethBuyOrder, ethSellOrder} "ETH signed order" should "recover signer public key correctly" in { val testOrder = Order( - Order.V1, - PublicKey(EthStubBytes32), - PublicKey(EthStubBytes32), + Order.V4, + OrderAuthentication(TestEthOrdersPublicKey), + TestEthOrdersPublicKey, AssetPair(IssuedAsset(ByteStr(EthStubBytes32)), IssuedAsset(ByteStr(EthStubBytes32))), OrderType.BUY, 1, @@ -72,7 +40,7 @@ class EthOrderSpec val signature = EthEncoding.toBytes( - "0x54119bc5b24d9363b7a1a31a71a2e6194dfeedc5e9644893b0a04bb57004e5b14342c1ce29ee00877da49180fd6d7fb332ff400231f809da7ed0dcb07c504e2d1c" + "0xe4b329ea85ab9b82fda55f6bf063170864fbb66651dbf5d7e8278a79df2a46d26400f966b49c5a46c82cea590538569dc058facaa43b6246b3dce0d37fae6b4a1b" ) val result = EthOrders.recoverEthSignerKey(testOrder, signature) @@ -80,31 +48,24 @@ class EthOrderSpec result.toAddress shouldBe TestEthOrdersPublicKey.toAddress } - it should "be of version 4" in { - val testOrder = Order( - Order.V1, - PublicKey(EthStubBytes32), - PublicKey(EthStubBytes32), - AssetPair(IssuedAsset(ByteStr(EthStubBytes32)), Waves), - OrderType.BUY, - 1, - 1, - 123, - 321, - 1, - Waves, - eip712Signature = EthSignature( - "0xb557dae4c614146dd35ba6fd80e4702a75d33ffcb8af09e80e0c1a7386b8ffcb5b76bd8037f6484de809a80a5b39a224301c76e8bad9b1a9e7ada53ba6fa7e361c" - ) - ) + it should "recover public key at json parse stage" in { + import com.wavesplatform.transaction.assets.exchange.OrderJson.orderReads - testOrder.isValid(123).labels shouldBe Set("eip712Signature available only in V4") + val json = Json.toJson(ethBuyOrder).as[JsObject] - "senderPublicKey" + val order = Json.fromJson[Order](json).get + order.senderPublicKey shouldBe ethBuyOrder.senderPublicKey + + intercept[IllegalArgumentException](Json.fromJson[Order](json - "eip712Signature")).getMessage should include( + "Either senderPublicKey or eip712Signature should be provided" + ) } - it should "be not contain proofs" in { + it should "be of version 4" in { val testOrder = Order( - Order.V4, - PublicKey(EthStubBytes32), + Order.V1, + EthSignature( + "0xb557dae4c614146dd35ba6fd80e4702a75d33ffcb8af09e80e0c1a7386b8ffcb5b76bd8037f6484de809a80a5b39a224301c76e8bad9b1a9e7ada53ba6fa7e361c" + ), PublicKey(EthStubBytes32), AssetPair(IssuedAsset(ByteStr(EthStubBytes32)), Waves), OrderType.BUY, @@ -113,14 +74,10 @@ class EthOrderSpec 123, 321, 1, - Waves, - Proofs(ByteStr.empty), - eip712Signature = EthSignature( - "0xb557dae4c614146dd35ba6fd80e4702a75d33ffcb8af09e80e0c1a7386b8ffcb5b76bd8037f6484de809a80a5b39a224301c76e8bad9b1a9e7ada53ba6fa7e361c" - ) + Waves ) - testOrder.isValid(123).labels shouldBe Set("eip712Signature excludes proofs") + testOrder.isValid(123).labels shouldBe Set("eip712Signature available only in V4") } it should "work in exchange transaction" in { @@ -137,7 +94,7 @@ class EthOrderSpec diff should containAppliedTx(transaction.id()) } - it should "work in exchange transaction with old order" in { + it should "work in exchange transaction with an old order" in { val blockchain = createBlockchainStub { blockchain => val sh = StubHelpers(blockchain) sh.creditBalance(TxHelpers.matcher.toAddress, *) @@ -165,6 +122,106 @@ class EthOrderSpec diff should containAppliedTx(transaction.id()) } + it should "recover valid ids of exchange tx" in { + val blockchain = createBlockchainStub { blockchain => + val sh = StubHelpers(blockchain) + sh.creditBalance(TxHelpers.matcher.toAddress, *) + sh.creditBalance(TestEthOrdersPublicKey.toAddress, *) + sh.issueAsset(ByteStr(EthStubBytes32)) + } + + val buyOrder = Order + .selfSigned( + Order.V3, + TxHelpers.defaultSigner, + TxHelpers.matcher.publicKey, + AssetPair(IssuedAsset(ByteStr(EthStubBytes32)), Waves), + OrderType.BUY, + 1, + 100L, + 1, + 123, + 100000, + Waves + ) + .withProofs(TxHelpers.signature("2Bi5YFCeAUvQqWFJYUTzaDUfAdoHmQ4RC6nviBwvQgUYJLKrsa4T5eESGr5Er261kdeyNgHVJUGai8mALtLLWDoQ")) + + val sellOrder = ethSellOrder.copy(orderAuthentication = + EthSignature( + "0x6c4385dd5f6f1200b4d0630c9076104f34c801c16a211e505facfd743ba242db4429b966ffa8d2a9aff9037dafda78cfc8f7c5ef1c94493f5954bc7ebdb649281b" + ) + ) + + StubHelpers(blockchain).creditBalance(sellOrder.senderAddress, *) + + val transaction = TxHelpers + .exchange(buyOrder, sellOrder, TxVersion.V3, 100) + .copy(proofs = TxHelpers.signature("4WrABDgkk9JraBLNQK4LTq7LWqVLgLzAEv8fr1rjr4ovca7224EBzLrEgcHdtHscGpQbLsk39ttQfqHMVLr9tXcB")) + + transaction.json() should matchJson("""{ + | "type": 7, + | "id": "GNz9EGRPNroTXhQ4Kjz2Qb4u3oBoRdteRTYwsHfDHJW5", + | "fee": 1000000, + | "feeAssetId": null, + | "timestamp": 100, + | "version": 3, + | "chainId": 69, + | "sender": "3FrCwv8uFRxQazhX6Lno45aZ68Bof6ScaeF", + | "senderPublicKey": "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", + | "proofs": [ + | "4WrABDgkk9JraBLNQK4LTq7LWqVLgLzAEv8fr1rjr4ovca7224EBzLrEgcHdtHscGpQbLsk39ttQfqHMVLr9tXcB" + | ], + | "order1": { + | "version": 3, + | "id": "75YqwVQbiQmLMQBE61W1aLcsaAUnWbzM5Udh9Z4mXUBf", + | "sender": "3FrCwv8uFRxQazhX6Lno45aZ68Bof6ScaeF", + | "senderPublicKey": "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", + | "matcherPublicKey": "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", + | "assetPair": { + | "amountAsset": "5fQPsn8hoaVddFG26cWQ5QFdqxWtUPNaZ9zH2E6LYzFn", + | "priceAsset": null + | }, + | "orderType": "buy", + | "amount": 1, + | "price": 100, + | "timestamp": 1, + | "expiration": 123, + | "matcherFee": 100000, + | "signature": "2Bi5YFCeAUvQqWFJYUTzaDUfAdoHmQ4RC6nviBwvQgUYJLKrsa4T5eESGr5Er261kdeyNgHVJUGai8mALtLLWDoQ", + | "proofs": [ + | "2Bi5YFCeAUvQqWFJYUTzaDUfAdoHmQ4RC6nviBwvQgUYJLKrsa4T5eESGr5Er261kdeyNgHVJUGai8mALtLLWDoQ" + | ], + | "matcherFeeAssetId": null + | }, + | "order2": { + | "version": 4, + | "id": "8274Mc8WiNQdP3YhinBGkEX79AcZe5th51DJCTW8rEUZ", + | "sender": "3G9uRSP4uVjTFjGZixYW4arBZUKWHxjnfeW", + | "senderPublicKey": "5vwTDMooR7Hp57MekN7qHz7fHNVrkn2Nx4CiWdq4cyBR4LNnZWYAr7UfBbzhmSvtNkv6e45aJ4Q4aKCSinyHVw33", + | "matcherPublicKey": "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", + | "assetPair": { + | "amountAsset": "5fQPsn8hoaVddFG26cWQ5QFdqxWtUPNaZ9zH2E6LYzFn", + | "priceAsset": null + | }, + | "orderType": "sell", + | "amount": 1, + | "price": 100, + | "timestamp": 1, + | "expiration": 123, + | "matcherFee": 100000, + | "signature": "", + | "proofs": [], + | "matcherFeeAssetId": null, + | "eip712Signature": "0x6c4385dd5f6f1200b4d0630c9076104f34c801c16a211e505facfd743ba242db4429b966ffa8d2a9aff9037dafda78cfc8f7c5ef1c94493f5954bc7ebdb649281b", + | "priceMode": null + | }, + | "amount": 1, + | "price": 100, + | "buyMatcherFee": 100000, + | "sellMatcherFee": 100000 + |}""".stripMargin) + } + it should "not work in exchange transaction with changed signature" in { val blockchain = createBlockchainStub { blockchain => val sh = StubHelpers(blockchain) @@ -177,15 +234,15 @@ class EthOrderSpec val transaction = TxHelpers .exchange(ethBuyOrder, ethSellOrder, TxVersion.V3, 100) .copy( - order2 = ethSellOrder.copy( - eip712Signature = EthSignature( + order2 = ethSellOrder.copy(orderAuthentication = + EthSignature( "0x1717804a1d60149988821546732442eabc69f46b2764e231eaeef48351d9f36577278c3f29fe3d61500932190dba8c045b19acda117a4690bfd3d2c28bb67bf91c" ) ) ) differ(transaction).resultE should matchPattern { - case Left(err) if err.toString.contains("Proof doesn't validate as signature") => + case Left(err) if err.toString.contains("negative waves balance") => } } @@ -234,16 +291,16 @@ class EthOrderSpec val script = TxHelpers.script( """ - |{-# STDLIB_VERSION 5 #-} - |{-# CONTENT_TYPE EXPRESSION #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - | - | - |match tx { - | case e: ExchangeTransaction => if (e.buyOrder.proofs[0] == base58'' && e.sellOrder.proofs[0] == base58'') then true else throw("Only ethereum") - | case _: Order => true - | case _ => false - |}""".stripMargin + |{-# STDLIB_VERSION 5 #-} + |{-# CONTENT_TYPE EXPRESSION #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + | + |match tx { + | case e: ExchangeTransaction => if (e.buyOrder.proofs[0] == base58'' && e.sellOrder.proofs[0] == base58'') then true else throw("Only ethereum") + | case _: Order => true + | case _ => false + |}""".stripMargin ) sh.setScript(TxHelpers.matcher.toAddress, script) } @@ -254,3 +311,47 @@ class EthOrderSpec diff should containAppliedTx(transaction.id()) } } + +object EthOrderSpec extends EthHelpers { + + /** Use for create a hardcoded signature for a test order + * @param order + * Order parameters + */ + def signOrder(order: Order): Unit = { + val signature = EthOrders.signOrder(order, TxHelpers.defaultEthSigner) + println(EthEncoding.toHexString(signature)) + } + + val ethBuyOrder: Order = Order( + Order.V4, + EthSignature( + "0x0a897d382e4e4a066e1d98e5c3c1051864a557c488571ff71e036c0f5a2c7204274cb293cd4aa7ad40f8c2f650e1a2770ecca6aa14a1da883388fa3b5b9fa8b71c" + ), + TxHelpers.matcher.publicKey, + AssetPair(IssuedAsset(ByteStr(EthStubBytes32)), Waves), + OrderType.BUY, + 1, + 100L, + 1, + 123, + 100000, + Waves + ) + + val ethSellOrder: Order = Order( + Order.V4, + EthSignature( + "0x6c4385dd5f6f1200b4d0630c9076104f34c801c16a211e505facfd743ba242db4429b966ffa8d2a9aff9037dafda78cfc8f7c5ef1c94493f5954bc7ebdb649281b" + ), + TxHelpers.matcher.publicKey, + AssetPair(IssuedAsset(ByteStr(EthStubBytes32)), Waves), + OrderType.SELL, + 1, + 100L, + 1, + 123, + 100000, + Waves + ) +} diff --git a/node/src/test/scala/com/wavesplatform/transaction/assets/exchange/OrderJsonSpecification.scala b/node/src/test/scala/com/wavesplatform/transaction/assets/exchange/OrderJsonSpecification.scala index e97446fdef1..91b0fadb7e5 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/assets/exchange/OrderJsonSpecification.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/assets/exchange/OrderJsonSpecification.scala @@ -5,11 +5,13 @@ import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, EitherExt2} import com.wavesplatform.test.PropSpec import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} -import com.wavesplatform.transaction.assets.exchange.OrderJson._ +import com.wavesplatform.transaction.assets.exchange.OrderJson.* import com.wavesplatform.transaction.smart.Verifier -import play.api.libs.json._ +import com.wavesplatform.transaction.Proofs +import com.wavesplatform.utils.{EthEncoding, EthHelpers, JsonMatchers} +import play.api.libs.json.* -class OrderJsonSpecification extends PropSpec { +class OrderJsonSpecification extends PropSpec with JsonMatchers with EthHelpers { property("Read Order from json") { val keyPair = KeyPair("123".getBytes("UTF-8")) @@ -83,6 +85,83 @@ class OrderJsonSpecification extends PropSpec { o.signature shouldBe ByteStr(Base58.decode("signature")) o.matcherFeeAssetId shouldBe IssuedAsset(ByteStr.decodeBase58("29ot86P3HoUZXH1FCoyvff7aeZ3Kt7GqPwBWXncjRF2b").get) } + + val jsonOV4 = Json.parse(s""" + { + "version": 4, + "senderPublicKey": "$pubKeyStr", + "matcherPublicKey": "DZUxn4pC7QdYrRqacmaAJghatvnn1Kh1mkE2scZoLuGJ", + "assetPair": { + "amountAsset": "29ot86P3HoUZXH1FCoyvff7aeZ3Kt7GqPwBWXncjRF2b", + "priceAsset": "GEtBMkg419zhDiYRXKwn2uPcabyXKqUqj4w3Gcs1dq44" + }, + "orderType": "buy", + "amount": 0, + "matcherFee": 0, + "price": 0, + "timestamp": 0, + "expiration": 0, + "signature": "signature", + "matcherFeeAssetId": "29ot86P3HoUZXH1FCoyvff7aeZ3Kt7GqPwBWXncjRF2b" + } """) + + jsonOV4.validate[Order] match { + case JsError(e) => + fail("Error: " + e.toString()) + case JsSuccess(o, _) => + o.id().toString shouldBe "GgLcdyNR5XCvqtGDh9cfFWQzU7sUGLkWQ6NnJufQfNhX" + o.senderPublicKey shouldBe keyPair.publicKey + o.matcherPublicKey shouldBe PublicKey(Base58.tryDecodeWithLimit("DZUxn4pC7QdYrRqacmaAJghatvnn1Kh1mkE2scZoLuGJ").get) + o.assetPair.amountAsset shouldBe IssuedAsset(ByteStr.decodeBase58("29ot86P3HoUZXH1FCoyvff7aeZ3Kt7GqPwBWXncjRF2b").get) + o.assetPair.priceAsset shouldBe IssuedAsset(ByteStr.decodeBase58("GEtBMkg419zhDiYRXKwn2uPcabyXKqUqj4w3Gcs1dq44").get) + o.price shouldBe 0 + o.amount shouldBe 0 + o.matcherFee shouldBe 0 + o.timestamp shouldBe 0 + o.expiration shouldBe 0 + o.signature shouldBe ByteStr(Base58.decode("signature")) + o.matcherFeeAssetId shouldBe IssuedAsset(ByteStr.decodeBase58("29ot86P3HoUZXH1FCoyvff7aeZ3Kt7GqPwBWXncjRF2b").get) + } + + + val jsonOV4WithEthSig = Json.parse(s""" + { + "version": 4, + "matcherPublicKey": "DZUxn4pC7QdYrRqacmaAJghatvnn1Kh1mkE2scZoLuGJ", + "assetPair": { + "amountAsset": "29ot86P3HoUZXH1FCoyvff7aeZ3Kt7GqPwBWXncjRF2b", + "priceAsset": "GEtBMkg419zhDiYRXKwn2uPcabyXKqUqj4w3Gcs1dq44" + }, + "orderType": "buy", + "amount": 0, + "matcherFee": 0, + "price": 0, + "timestamp": 0, + "expiration": 0, + "signature": "signature", + "matcherFeeAssetId": "29ot86P3HoUZXH1FCoyvff7aeZ3Kt7GqPwBWXncjRF2b", + "eip712Signature": "0x40dd06c9f80215612a0397948a10dd82d6a58dda8a256544971e236a95a395ad6b87e75fb58789ece4f2ff7ed380849d120faefce135b6f7ddec9e11df169f971b" + } """) + + jsonOV4WithEthSig.validate[Order] match { + case JsError(e) => + fail("Error: " + e.toString()) + case JsSuccess(o, _) => + o.id().toString shouldBe "24zTji9WLBEb4DrY4UhZAqN6aVrk7VYLNFdLg4J2c4g7" + o.withProofs(Proofs.empty).id() shouldNot be(o.id()) + o.senderPublicKey shouldBe TestEthOrdersPublicKey + o.matcherPublicKey shouldBe PublicKey(Base58.tryDecodeWithLimit("DZUxn4pC7QdYrRqacmaAJghatvnn1Kh1mkE2scZoLuGJ").get) + o.assetPair.amountAsset shouldBe IssuedAsset(ByteStr.decodeBase58("29ot86P3HoUZXH1FCoyvff7aeZ3Kt7GqPwBWXncjRF2b").get) + o.assetPair.priceAsset shouldBe IssuedAsset(ByteStr.decodeBase58("GEtBMkg419zhDiYRXKwn2uPcabyXKqUqj4w3Gcs1dq44").get) + o.price shouldBe 0 + o.amount shouldBe 0 + o.matcherFee shouldBe 0 + o.timestamp shouldBe 0 + o.expiration shouldBe 0 + o.signature shouldBe ByteStr.empty + o.matcherFeeAssetId shouldBe IssuedAsset(ByteStr.decodeBase58("29ot86P3HoUZXH1FCoyvff7aeZ3Kt7GqPwBWXncjRF2b").get) + o.eip712Signature shouldBe Some(ByteStr(EthEncoding.toBytes("0x40dd06c9f80215612a0397948a10dd82d6a58dda8a256544971e236a95a395ad6b87e75fb58789ece4f2ff7ed380849d120faefce135b6f7ddec9e11df169f971b"))) + } } property("Read Order without sender and matcher PublicKey") { @@ -143,9 +222,9 @@ class OrderJsonSpecification extends PropSpec { json.validate[Order] match { case e: JsError => fail("Error: " + JsError.toJson(e).toString()) - case s: JsSuccess[Order] => - val o = s.get - o.json().toString() should be(json.toString()) + + case JsSuccess(o: Order, _) => + o.json() should matchJson(json) Verifier.verifyAsEllipticCurveSignature(o, checkWeakPk = false).explicitGet() } } diff --git a/node/src/test/scala/com/wavesplatform/utils/EthHelpers.scala b/node/src/test/scala/com/wavesplatform/utils/EthHelpers.scala index 5ddce4467e1..4ed4adda56e 100644 --- a/node/src/test/scala/com/wavesplatform/utils/EthHelpers.scala +++ b/node/src/test/scala/com/wavesplatform/utils/EthHelpers.scala @@ -5,6 +5,7 @@ import java.math.BigInteger import com.wavesplatform.account.{Address, AddressScheme, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.transaction.{EthereumTransaction, TxHelpers} +import com.wavesplatform.transaction.assets.exchange.OrderAuthentication import com.wavesplatform.transaction.utils.EthTxGenerator import org.scalatest.{BeforeAndAfterEach, Suite} import org.web3j.crypto.{Bip32ECKeyPair, RawTransaction, SignedRawTransaction} @@ -14,13 +15,11 @@ trait EthHelpers { val EthStubBytes32: Array[Byte] = Array.fill(32)(EthChainId.byte) object EthSignature { - def apply(str: String): Option[ByteStr] = Some(ByteStr(EthEncoding.toBytes(str))) + def apply(str: String): OrderAuthentication.Eip712Signature = OrderAuthentication.Eip712Signature(ByteStr(EthEncoding.toBytes(str))) } val TestEthOrdersPublicKey: PublicKey = PublicKey( - EthEncoding.toBytes( - "0xd10a150ba9a535125481e017a09c2ac6a1ab43fc43f7ab8f0d44635106672dd7de4f775c06b730483862cbc4371a646d86df77b3815593a846b7272ace008c42" - ) + EthEncoding.toBytes(TxHelpers.defaultEthSigner.getPublicKey.toString(16)) ) val TestEthRawTransaction: RawTransaction =