diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SyncOperatorLsuIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SyncOperatorLsuIntegrationTest.scala index b5990858a11..8138b1c6d89 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SyncOperatorLsuIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SyncOperatorLsuIntegrationTest.scala @@ -172,7 +172,7 @@ class SyncOperatorLsuIntegrationTest actAndCheck( "alice buys traffic for the operator's synchronizer", - buyTraffic(aliceParty, member, synchronizerId, registration, dsoParty, purchase), + buyTraffic(aliceParty, member, synchronizerId, Some(registration), dsoParty, purchase), )( "the operator grants it on the synchronizer it is about to upgrade", _ => extraTrafficLimit(member) shouldBe purchase, @@ -266,7 +266,7 @@ class SyncOperatorLsuIntegrationTest actAndCheck(10.minutes)( "alice buys traffic again once the upgrade has landed", - buyTraffic(aliceParty, member, synchronizerId, registration, dsoParty, purchase), + buyTraffic(aliceParty, member, synchronizerId, Some(registration), dsoParty, purchase), )( "the operator grants it on the successor's sequencer, so it switched over too", _ => diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SyncOperatorTrafficIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SyncOperatorTrafficIntegrationTest.scala index f7371375f76..83326311dc5 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SyncOperatorTrafficIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SyncOperatorTrafficIntegrationTest.scala @@ -6,6 +6,7 @@ package org.lfdecentralizedtrust.splice.integration.tests import com.digitalasset.canton.SynchronizerAlias import com.digitalasset.canton.config.NonNegativeFiniteDuration import com.digitalasset.canton.config.RequireTypes.{NonNegativeLong, NonNegativeNumeric} +import com.digitalasset.canton.topology.SynchronizerId import monocle.macros.syntax.lens.* import org.lfdecentralizedtrust.splice.codegen.java.splice.decentralizedsynchronizer.GovernanceParameters import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.topupstate.ValidatorTopUpState @@ -42,6 +43,7 @@ class SyncOperatorTrafficIntegrationTest private val firstPurchase = 1_000_000L private val secondPurchase = 2_000_000L private val walletRequestPurchase = 3_000_000L + private val discount = BigDecimal("0.5") private val splitwellAlias = SynchronizerAlias.tryCreate("splitwell") override def environmentDefinition: SpliceEnvironmentDefinition = @@ -123,7 +125,7 @@ class SyncOperatorTrafficIntegrationTest .exerciseDsoRules_RegisterSynchronizer( synchronizerId.toProtoPrimitive, operatorParty.toProtoPrimitive, - new GovernanceParameters(java.math.BigDecimal.ONE.setScale(10)), + new GovernanceParameters(discount.bigDecimal.setScale(10)), ) .commands .asScala @@ -139,6 +141,10 @@ class SyncOperatorTrafficIntegrationTest sv1ScanBackend.lookupSynchronizerRegistration(synchronizerId.toProtoPrimitive).value } + clue("the registration carries the discount") { + BigDecimal(registration.payload.governanceParameters.discountFactor) shouldBe discount + } + clue("the validator serves the registration to its wallet clients through the scan proxy") { aliceValidatorBackend.scanProxy .lookupSynchronizerRegistration(synchronizerId.toProtoPrimitive) @@ -156,9 +162,9 @@ class SyncOperatorTrafficIntegrationTest trafficState(member).map(_.state.baseTrafficRemainder.value) shouldBe Some(0L) } - actAndCheck( + val (dedicatedPurchase, _) = actAndCheck( "alice buys traffic for the splitwell synchronizer", - buyTraffic(aliceParty, member, synchronizerId, registration, dsoParty, firstPurchase), + buyTraffic(aliceParty, member, synchronizerId, Some(registration), dsoParty, firstPurchase), )( "the purchase is granted on the splitwell sequencer", _ => extraTrafficLimit(member) shouldBe firstPurchase, @@ -172,9 +178,32 @@ class SyncOperatorTrafficIntegrationTest } } + clue("the purchase costs the global synchronizer's price at the discount") { + val globalSynchronizerId = + aliceValidatorBackend.participantClientWithAdminToken.synchronizers + .id_of(SynchronizerAlias.tryCreate("global")) + .logical + val globalPurchase = + buyTraffic(aliceParty, member, globalSynchronizerId, None, dsoParty, firstPurchase) + // Allows for Daml rounding each step to ten decimal places. + BigDecimal(dedicatedPurchase.amuletPaid) shouldBe + (BigDecimal(globalPurchase.amuletPaid) * discount +- BigDecimal("0.000001")) + } + + clue("a purchase for a synchronizer that is neither required nor registered is refused") { + val unregisteredSynchronizerId = + SynchronizerId.tryFromString( + s"unregistered::${synchronizerId.namespace.toProtoPrimitive}" + ) + assertThrowsAndLogsCommandFailures( + buyTraffic(aliceParty, member, unregisteredSynchronizerId, None, dsoParty, firstPurchase), + _.errorMessage should include("Unknown synchronizer provided"), + ) + } + actAndCheck( "alice buys a second traffic amount", - buyTraffic(aliceParty, member, synchronizerId, registration, dsoParty, secondPurchase), + buyTraffic(aliceParty, member, synchronizerId, Some(registration), dsoParty, secondPurchase), )( "the limit rises by exactly the second amount", _ => extraTrafficLimit(member) shouldBe (firstPurchase + secondPurchase), diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/SyncOperatorTestUtil.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/SyncOperatorTestUtil.scala index 70ac76a5c3d..f74537b0050 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/SyncOperatorTestUtil.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/SyncOperatorTestUtil.scala @@ -31,10 +31,14 @@ trait SyncOperatorTestUtil extends TestCommon { buyer: PartyId, member: Member, synchronizerId: SynchronizerId, - registration: ContractWithState[RegisteredSynchronizer.ContractId, RegisteredSynchronizer], + registration: Option[ + ContractWithState[RegisteredSynchronizer.ContractId, RegisteredSynchronizer] + ], dsoParty: PartyId, trafficAmount: Long, - )(implicit env: SpliceTestConsoleEnvironment): Unit = { + )(implicit + env: SpliceTestConsoleEnvironment + ): splice.amuletrules.AmuletRules_BuyMemberTrafficResult = { val transferContext = sv1ScanBackend.getTransferContextWithInstances(CantonTimestamp.now()) val amulets = aliceWalletClient.list().amulets.map(_.contract.contractId.contractId) @@ -66,16 +70,16 @@ trait SyncOperatorTestUtil extends TestCommon { 0L, trafficAmount, Some(dsoParty.toProtoPrimitive).toJava, - Some(registration.contractId).toJava, + registration.map(_.contractId).toJava, ), disclosedContracts = DisclosedContracts .forTesting( transferContext.amuletRules, - transferContext.latestOpenMiningRound, - registration, + (Seq[ContractWithState[?, ?]](transferContext.latestOpenMiningRound) ++ registration)* ) .toLedgerApiDisclosedContracts, ) + .exerciseResult } protected def trafficState(