diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AllocationsFrontendIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AllocationsFrontendIntegrationTest.scala index 50d61ce7a8..09cb59e745 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AllocationsFrontendIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AllocationsFrontendIntegrationTest.scala @@ -10,7 +10,6 @@ import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.allocationv TransferLeg as TransferLegV2, Reference as SettlementReference, } -import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.holdingv2.InstrumentId import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.metadatav1.Metadata import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.SpliceTestConsoleEnvironment @@ -21,7 +20,6 @@ import org.lfdecentralizedtrust.splice.util.{ WalletTestUtil, } -import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit import java.time.{LocalDateTime, ZoneOffset} import java.util.Optional @@ -61,43 +59,43 @@ class AllocationsFrontendIntegrationTest webDriver: WebDriverType, ) = { val validatorPartyId = aliceValidatorBackend.getValidatorPartyId() - val receiver = validatorPartyId val now = LocalDateTime .now() .truncatedTo(ChronoUnit.MICROS) .toInstant(ZoneOffset.UTC) - val requestedAt = now.minusSeconds(1800) - val allocateBefore = now.plusSeconds(3600) val settleBefore = now.plusSeconds(3600 * 2) + val wantedTransferLegs = Seq( + new TransferLegV2( + "oneway", + basicAccount(sender), + basicAccount(validatorPartyId), + BigDecimal(12).bigDecimal.setScale(10), + amuletInstrumentIdName, + new Metadata(java.util.Map.of("k3", "v3")), + ), + new TransferLegV2( + "waybackbutless", + basicAccount(validatorPartyId), + basicAccount(sender), + BigDecimal(6).bigDecimal.setScale(10), + amuletInstrumentIdName, + new Metadata(java.util.Map.of("k3", "v3")), + ), + ) val wantedAllocation = new AllocationSpecification( new SettlementInfo( java.util.List.of(validatorPartyId.toProtoPrimitive), new SettlementReference("some_reference", Optional.empty), - requestedAt, - allocateBefore, java.util.Optional.of(settleBefore), new Metadata(java.util.Map.of("k1", "v1", "k2", "v2")), ), - java.util.List.of( - new TransferLegV2( - "oneway", - basicAccount(sender), - basicAccount(receiver), - BigDecimal(12).bigDecimal.setScale(10), - new InstrumentId(dsoParty.toProtoPrimitive, "Amulet"), - new Metadata(java.util.Map.of("k3", "v3")), - ), - new TransferLegV2( - "waybackbutless", - basicAccount(receiver), - basicAccount(sender), - BigDecimal(6).bigDecimal.setScale(10), - new InstrumentId(dsoParty.toProtoPrimitive, "Amulet"), - new Metadata(java.util.Map.of("k3", "v3")), - ), - ), + dsoParty.toProtoPrimitive, basicAccount(sender), + wantedTransferLegs.map(transferLegSideForAuthorizer(sender, _)).asJava, + Optional.empty[java.util.Map[String, java.math.BigDecimal]](), + false, + new Metadata(java.util.Map.of("k1", "v1", "k2", "v2")), ) browseToAllocationsPage() @@ -113,10 +111,10 @@ class AllocationsFrontendIntegrationTest validatorPartyId.toProtoPrimitive, ) // Add n (-1 because one is already there) forms for transfer legs - wantedAllocation.transferLegs.asScala.drop(1).foreach { _ => + wantedTransferLegs.drop(1).foreach { _ => eventuallyClickOn(id("add-transfer-leg")) } - wantedAllocation.transferLegs.asScala.zipWithIndex.foreach { case (transferLeg, index) => + wantedTransferLegs.zipWithIndex.foreach { case (transferLeg, index) => textField(s"create-allocation-transfer-leg-id-$index").underlying .sendKeys(transferLeg.transferLegId) eventuallyClickOn(id(s"create-allocation-transfer-leg-sender-$index")) @@ -138,21 +136,6 @@ class AllocationsFrontendIntegrationTest ) } - val allocationTimestampFormat = - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'") - textField("create-allocation-settlement-requested-at").underlying - .sendKeys( - allocationTimestampFormat.format( - wantedAllocation.settlement.requestedAt.atOffset(ZoneOffset.UTC) - ) - ) - textField("create-allocation-settlement-settle-at").underlying - .sendKeys( - allocationTimestampFormat.format( - wantedAllocation.settlement.settleAt.atOffset(ZoneOffset.UTC) - ) - ) - eventuallyClickOn(id("create-allocation-submit-button")) }, )( @@ -169,7 +152,7 @@ class AllocationsFrontendIntegrationTest checkTransferLegsV2( allocation, - wantedAllocation.transferLegs.asScala.toSeq, + wantedTransferLegs, ) }, ) @@ -221,7 +204,7 @@ class AllocationsFrontendIntegrationTest Seq(venueParty.toProtoPrimitive), ) - checkTransferLegsV2(allocationRequest, otcTrade.data.transferLegs.asScala.toSeq) + checkTransferLegsV2(allocationRequest, otcTrade.data.tradeLegs.asScala.map(_.leg).toSeq) allocationRequest } @@ -250,7 +233,7 @@ class AllocationsFrontendIntegrationTest Seq(venueParty.toProtoPrimitive), ) - checkTransferLegsV2(allocation, otcTrade.data.transferLegs.asScala.toSeq) + checkTransferLegsV2(allocation, otcTrade.data.tradeLegs.asScala.map(_.leg).toSeq) allocation }, @@ -480,7 +463,7 @@ class AllocationsFrontendIntegrationTest checkTransferLeg( row = row, legId = transferLeg.transferLegId, - instrumentId = transferLeg.instrumentId.id, + instrumentId = transferLeg.instrumentId, amount = transferLeg.amount, sender = transferLeg.sender.owner, receiver = transferLeg.receiver.owner, diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AmuletAllocationsIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AmuletAllocationsIntegrationTest.scala index bca2e8f14a..f4cb20fcfe 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AmuletAllocationsIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AmuletAllocationsIntegrationTest.scala @@ -6,7 +6,6 @@ import com.digitalasset.canton.topology.PartyId import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.allocationv1 import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.allocationv2 import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.holdingv1 -import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.holdingv2 import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.metadatav1.Metadata import org.lfdecentralizedtrust.splice.http.v0.definitions.AllocationInstructionResultOutput.members import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition @@ -31,8 +30,6 @@ class AmuletAllocationsIntegrationTest .simpleTopology1Sv(this.getClass.getSimpleName) } - private val someMetadata = new Metadata(java.util.Map.of("k1", "v1", "k2", "v2")) - private def createAllocationV1(sender: PartyId)(implicit ev: SpliceTestConsoleEnvironment ) = { @@ -48,7 +45,7 @@ class AmuletAllocationsIntegrationTest requestedAt.toInstant, allocateBefore.toInstant, settleBefore.toInstant, - someMetadata, + new Metadata(java.util.Map.of("k1", "v1", "k2", "v2")), ), "some_transfer_leg_id", new allocationv1.TransferLeg( @@ -56,7 +53,7 @@ class AmuletAllocationsIntegrationTest receiver.toProtoPrimitive, BigDecimal(12).bigDecimal.setScale(10), new holdingv1.InstrumentId(dsoParty.toProtoPrimitive, "Amulet"), - someMetadata, + new Metadata(java.util.Map.of("k3", "v3")), ), ) @@ -68,34 +65,37 @@ class AmuletAllocationsIntegrationTest ev: SpliceTestConsoleEnvironment ) = { val validatorPartyId = aliceValidatorBackend.getValidatorPartyId() - val receiver = validatorPartyId val now = CantonTimestamp.now() - val allocateBefore = now.plusSeconds(3600) val settlementDeadline = now.plusSeconds(3600 * 2) - def wantedAllocationV2(requestedAt: CantonTimestamp) = new allocationv2.AllocationSpecification( + def wantedAllocationV2() = new allocationv2.AllocationSpecification( new allocationv2.SettlementInfo( java.util.List.of(validatorPartyId.toProtoPrimitive), new allocationv2.Reference("some_reference", Optional.empty), - requestedAt.toInstant, - allocateBefore.toInstant, java.util.Optional.of(settlementDeadline.toInstant), - someMetadata, + new Metadata(java.util.Map.of("k1", "v1", "k2", "v2")), ), + dsoParty.toProtoPrimitive, + basicAccount(sender), java.util.List.of( - new allocationv2.TransferLeg( - "some_transfer_leg", - basicAccount(sender), - basicAccount(receiver), - BigDecimal(12).bigDecimal.setScale(10), - new holdingv2.InstrumentId(dsoParty.toProtoPrimitive, "Amulet"), - someMetadata, + transferLegSideForAuthorizer( + sender, + new allocationv2.TransferLeg( + "some_transfer_leg", + basicAccount(sender), + basicAccount(validatorPartyId), + BigDecimal(12).bigDecimal.setScale(10), + amuletInstrumentIdName, + new Metadata(java.util.Map.of("k3", "v3")), + ), ) ), - basicAccount(sender), + java.util.Optional.empty[java.util.Map[String, java.math.BigDecimal]](), + false, + new Metadata(java.util.Map.of("k4", "v4")), ) - val specification = wantedAllocationV2(now) + val specification = wantedAllocationV2() specification -> aliceWalletClient.allocateAmulet(specification) } diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardFetchFallbackIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardFetchFallbackIntegrationTest.scala index 74e18315f2..c804f061b9 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardFetchFallbackIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardFetchFallbackIntegrationTest.scala @@ -6,8 +6,7 @@ import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.allocationv import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.{allocationv2, metadatav1} import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.holdingv1.InstrumentId import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.holdingv2.{ - Account as AccountV2, - InstrumentId as InstrumentIdV2, + Account as AccountV2 } import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.transferinstructionv1.TransferInstruction import org.lfdecentralizedtrust.splice.http.v0.definitions.TransferInstructionResultOutput.members @@ -28,6 +27,7 @@ class TokenStandardFetchFallbackIntegrationTest with WalletTestUtil with WalletTxLogTestUtil with TriggerTestUtil + with TokenStandardV2TestUtil with HasActorSystem with HasExecutionContext { @@ -149,22 +149,27 @@ class TokenStandardFetchFallbackIntegrationTest new allocationv2.SettlementInfo( java.util.List.of(dsoParty.toProtoPrimitive), new allocationv2.Reference(referenceIdV2, Optional.empty), - Instant.now, - Instant.now.plusSeconds(3600L), Optional.of(Instant.now.plusSeconds(2 * 3600L)), new metadatav1.Metadata(java.util.Map.of()), ), + dsoParty.toProtoPrimitive, + new AccountV2(aliceParty.toProtoPrimitive, Optional.empty(), ""), java.util.List.of( - new allocationv2.TransferLeg( - UUID.randomUUID().toString, - new AccountV2(aliceParty.toProtoPrimitive, Optional.empty(), ""), - new AccountV2(bobParty.toProtoPrimitive, Optional.empty(), ""), - BigDecimal(10).bigDecimal, - new InstrumentIdV2(dsoParty.toProtoPrimitive, "Amulet"), - new metadatav1.Metadata(java.util.Map.of()), + transferLegSideForAuthorizer( + aliceParty, + new allocationv2.TransferLeg( + UUID.randomUUID().toString, + new AccountV2(aliceParty.toProtoPrimitive, Optional.empty(), ""), + new AccountV2(bobParty.toProtoPrimitive, Optional.empty(), ""), + BigDecimal(10).bigDecimal, + amuletInstrumentIdName, + new metadatav1.Metadata(java.util.Map.of()), + ), ) ), - new AccountV2(aliceParty.toProtoPrimitive, Optional.empty(), ""), + java.util.Optional.empty[java.util.Map[String, java.math.BigDecimal]](), + false, + new metadatav1.Metadata(java.util.Map.of()), ) ), )( diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardV2AllocationIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardV2AllocationIntegrationTest.scala index 458d79fd6c..dec4e173da 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardV2AllocationIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardV2AllocationIntegrationTest.scala @@ -43,6 +43,9 @@ class TokenStandardV2AllocationIntegrationTest with WalletTxLogTestUtil with TriggerTestUtil { + // TODO (#5499): restore once tx log parsing works for v2 + override protected def runTokenStandardCliSanityCheck: Boolean = false + override def environmentDefinition: EnvironmentDefinition = { EnvironmentDefinition .simpleTopology1Sv(this.getClass.getSimpleName) @@ -90,15 +93,21 @@ class TokenStandardV2AllocationIntegrationTest "OTCTrade", java.util.Optional.of(new metadatav1.AnyContract.ContractId(otcTrade.id.contractId)), ), - otcTrade.data.createdAt, - otcTrade.data.settleAt, otcTrade.data.settlementDeadline, emptyMetadata, ) val settleBatch = new allocationv2.SettlementFactory_SettleBatch( settlementInfo, - otcTrade.data.transferLegs, - allocations.asJava, + transferLegsFromTrade(otcTrade).asJava, + allocations + .map(cid => + new allocationv2.FinalizedAllocation( + cid, + java.util.List.of(), + java.util.Optional.empty[java.util.Map[String, java.math.BigDecimal]](), + ) + ) + .asJava, /*actors = */ java.util.List.of(venueParty.toProtoPrimitive), emptyExtraArgs, ) @@ -302,11 +311,16 @@ class TokenStandardV2AllocationIntegrationTest allocationRequestView: allocationrequestv2.AllocationRequestView, ) = { val allocateResponse = clue(s"${walletClient.name} accepts the Allocation Request") { + val requestedAllocation = allocationRequestView.allocations.asScala.loneElement walletClient.allocateAmulet( new allocationv2.AllocationSpecification( allocationRequestView.settlement, - allocationRequestView.transferLegs, + requestedAllocation.admin, allocationRequestView.authorizer, + requestedAllocation.transferLegSides, + requestedAllocation.nextIterationFunding, + requestedAllocation.committed, + requestedAllocation.meta, ) ) } diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardV2TestUtil.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardV2TestUtil.scala index b6c156e481..86a7827100 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardV2TestUtil.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardV2TestUtil.scala @@ -20,6 +20,8 @@ import scala.jdk.CollectionConverters.* trait TokenStandardV2TestUtil extends TestCommon { + protected val amuletInstrumentIdName = "Amulet" + protected val tokenStandardV2TestDarPath = "token-standard/examples/splice-token-test-trading-app-v2/.daml/dist/splice-token-test-trading-app-v2-current.dar" @@ -110,9 +112,15 @@ trait TokenStandardV2TestUtil extends TestCommon { bob: PartyId, bobTransferAmount: BigDecimal, ): tradingappv2.OTCTrade = { - val aliceLeg = mkTransferLeg("leg0", dso, alice, bob, aliceTransferAmount) + val aliceLeg = new tradingappv2.TradeLeg( + dso.toProtoPrimitive, + mkTransferLeg("leg0", alice, bob, aliceTransferAmount), + ) // TODO(#561): swap against a token from the token reference implementation - val bobLeg = mkTransferLeg("leg1", dso, bob, alice, bobTransferAmount) + val bobLeg = new tradingappv2.TradeLeg( + dso.toProtoPrimitive, + mkTransferLeg("leg1", bob, alice, bobTransferAmount), + ) new tradingappv2.OTCTrade( venue.toProtoPrimitive, Seq(aliceLeg, bobLeg).asJava, @@ -127,7 +135,6 @@ trait TokenStandardV2TestUtil extends TestCommon { def mkTransferLeg( legId: String, - dso: PartyId, sender: PartyId, receiver: PartyId, amount: BigDecimal, @@ -137,8 +144,38 @@ trait TokenStandardV2TestUtil extends TestCommon { basicAccount(sender), basicAccount(receiver), amount.bigDecimal, - new holdingv2.InstrumentId(dso.toProtoPrimitive, "Amulet"), + amuletInstrumentIdName, new metadatav1.Metadata(java.util.Map.of("some_leg_meta", UUID.randomUUID().toString)), ) + def transferLegSideForAuthorizer( + authorizer: PartyId, + transferLeg: allocationv2.TransferLeg, + ): allocationv2.TransferLegSide = { + val (side, otherside) = + if (transferLeg.sender.owner == authorizer.toProtoPrimitive) { + allocationv2.TransferSide.SENDERSIDE -> transferLeg.receiver + } else if (transferLeg.receiver.owner == authorizer.toProtoPrimitive) { + allocationv2.TransferSide.RECEIVERSIDE -> transferLeg.sender + } else { + throw new IllegalArgumentException( + s"Transfer leg `${transferLeg.transferLegId}` does not involve authorizer `${authorizer.toProtoPrimitive}`" + ) + } + + new allocationv2.TransferLegSide( + transferLeg.transferLegId, + side, + otherside, + transferLeg.amount, + transferLeg.instrumentId, + transferLeg.meta, + ) + } + + def transferLegsFromTrade( + otcTrade: tradingappv2.OTCTrade.Contract + ): Seq[allocationv2.TransferLeg] = + otcTrade.data.tradeLegs.asScala.map(_.leg).toSeq + } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/DarResources.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/DarResources.scala index 5f9f6c2ed9..85815dfb48 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/DarResources.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/DarResources.scala @@ -728,7 +728,7 @@ object DarResources { ) val apiTokenAllocationV2_1_0_0 = DarResource( "splice-api-token-allocation-v2-1.0.0.dar", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", PackageMetadata( PackageName.assertFromString("splice-api-token-allocation-v2"), PackageVersion.assertFromString("1.0.0"), @@ -772,7 +772,7 @@ object DarResources { ) val apiTokenAllocationV2_current = DarResource( "splice-api-token-allocation-v2-current.dar", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", PackageMetadata( PackageName.assertFromString("splice-api-token-allocation-v2"), PackageVersion.assertFromString("1.0.0"), @@ -821,13 +821,15 @@ object DarResources { ) val apiTokenAllocationRequestV2_1_0_0 = DarResource( "splice-api-token-allocation-request-v2-1.0.0.dar", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", PackageMetadata( PackageName.assertFromString("splice-api-token-allocation-request-v2"), PackageVersion.assertFromString("1.0.0"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", @@ -843,8 +845,6 @@ object DarResources { "e7e0adfa881e7dbbb07da065ae54444da7c4bccebcb8872ab0cb5dcf9f3761ce", "b70db8369e1c461d5c70f1c86f526a29e9776c655e6ffc2560f95b05ccb8b946", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", @@ -866,13 +866,15 @@ object DarResources { ) val apiTokenAllocationRequestV2_current = DarResource( "splice-api-token-allocation-request-v2-current.dar", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", PackageMetadata( PackageName.assertFromString("splice-api-token-allocation-request-v2"), PackageVersion.assertFromString("1.0.0"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", @@ -888,8 +890,6 @@ object DarResources { "e7e0adfa881e7dbbb07da065ae54444da7c4bccebcb8872ab0cb5dcf9f3761ce", "b70db8369e1c461d5c70f1c86f526a29e9776c655e6ffc2560f95b05ccb8b946", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", @@ -916,13 +916,15 @@ object DarResources { ) val apiTokenAllocationInstructionV2_1_0_0 = DarResource( "splice-api-token-allocation-instruction-v2-1.0.0.dar", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", PackageMetadata( PackageName.assertFromString("splice-api-token-allocation-instruction-v2"), PackageVersion.assertFromString("1.0.0"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", @@ -938,8 +940,6 @@ object DarResources { "e7e0adfa881e7dbbb07da065ae54444da7c4bccebcb8872ab0cb5dcf9f3761ce", "b70db8369e1c461d5c70f1c86f526a29e9776c655e6ffc2560f95b05ccb8b946", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", @@ -961,13 +961,15 @@ object DarResources { ) val apiTokenAllocationInstructionV2_current = DarResource( "splice-api-token-allocation-instruction-v2-current.dar", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", PackageMetadata( PackageName.assertFromString("splice-api-token-allocation-instruction-v2"), PackageVersion.assertFromString("1.0.0"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", @@ -983,8 +985,6 @@ object DarResources { "e7e0adfa881e7dbbb07da065ae54444da7c4bccebcb8872ab0cb5dcf9f3761ce", "b70db8369e1c461d5c70f1c86f526a29e9776c655e6ffc2560f95b05ccb8b946", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", @@ -1198,21 +1198,25 @@ object DarResources { ) val tokenTestTradingAppV2_1_0_0 = DarResource( "splice-token-test-trading-app-v2-1.0.0.dar", - "535f0008b0d4928342175d0c9e4a2a5f2a562a41debc7cb101f623a3b571df8c", + "da533befddd9bde0a0f7adb49dcd94374f25773e0aa592f2b06623df8030dfc4", PackageMetadata( PackageName.assertFromString("splice-token-test-trading-app-v2"), PackageVersion.assertFromString("1.0.0"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", + "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", "54f85ebfc7dfae18f7d70370015dcc6c6792f60135ab369c44ae52c6fc17c274", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -1222,21 +1226,17 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", - "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -1253,21 +1253,25 @@ object DarResources { ) val tokenTestTradingAppV2_current = DarResource( "splice-token-test-trading-app-v2-current.dar", - "535f0008b0d4928342175d0c9e4a2a5f2a562a41debc7cb101f623a3b571df8c", + "da533befddd9bde0a0f7adb49dcd94374f25773e0aa592f2b06623df8030dfc4", PackageMetadata( PackageName.assertFromString("splice-token-test-trading-app-v2"), PackageVersion.assertFromString("1.0.0"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", + "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", "54f85ebfc7dfae18f7d70370015dcc6c6792f60135ab369c44ae52c6fc17c274", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -1277,21 +1281,17 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", - "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -2164,23 +2164,25 @@ object DarResources { ) val amulet_0_1_19 = DarResource( "splice-amulet-0.1.19.dar", - "500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6", + "c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3", PackageMetadata( PackageName.assertFromString("splice-amulet"), PackageVersion.assertFromString("0.1.19"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", + "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", "54f85ebfc7dfae18f7d70370015dcc6c6792f60135ab369c44ae52c6fc17c274", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", "62a6fc80fc8ca84dd5f6b33c71f53dd35a02bc01784b8cbc7cbd283eb23863c0", "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", - "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -2191,21 +2193,17 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", - "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -2218,27 +2216,31 @@ object DarResources { "55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281", "cae345b5500ef6f84645c816f88b9f7a85a9f3c71697984abdf6849f81e80324", "91e167fa7a256f21f990c526a0a0df840e99aeef0e67dc1f5415b0309486de74", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", + "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", ), ) val amulet_current = DarResource( "splice-amulet-current.dar", - "500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6", + "c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3", PackageMetadata( PackageName.assertFromString("splice-amulet"), PackageVersion.assertFromString("0.1.19"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", + "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", "54f85ebfc7dfae18f7d70370015dcc6c6792f60135ab369c44ae52c6fc17c274", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", "62a6fc80fc8ca84dd5f6b33c71f53dd35a02bc01784b8cbc7cbd283eb23863c0", "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", - "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -2249,21 +2251,17 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", - "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -2276,6 +2274,8 @@ object DarResources { "55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281", "cae345b5500ef6f84645c816f88b9f7a85a9f3c71697984abdf6849f81e80324", "91e167fa7a256f21f990c526a0a0df840e99aeef0e67dc1f5415b0309486de74", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", + "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", ), ) val amulet = PackageResource( @@ -3477,23 +3477,23 @@ object DarResources { ) val dsoGovernance_0_1_25 = DarResource( "splice-dso-governance-0.1.25.dar", - "93645a39701e2ca0fbf1f5744df681ee11f0caed5f1d277a467498bab1ac0de1", + "6666c2be6d6d396900f198b58d34794f0faf1c9a90d595a8408d13298734bcab", PackageMetadata( PackageName.assertFromString("splice-dso-governance"), PackageVersion.assertFromString("0.1.25"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", "54f85ebfc7dfae18f7d70370015dcc6c6792f60135ab369c44ae52c6fc17c274", "62a6fc80fc8ca84dd5f6b33c71f53dd35a02bc01784b8cbc7cbd283eb23863c0", "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", - "f41642b5aa18385bf1ef8ab9272c041be1a3552d623bc0393f2ad03fe4cdf0ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", - "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -3504,24 +3504,20 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", + "b981de244e59400c97d0721622b9554d4b1b58a71a7c535de86f986c5ead0fe9", "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", - "500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6", - "78042a7322f7636af2a65bb0769116d2176317c59503821189e7681e1358cf43", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", + "67967ad0148807e3f5307da9faed89c393fad77595a350b2617fab8bdd02a967", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -3531,30 +3527,34 @@ object DarResources { "275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520", "e5411f3d75f072b944bd88e652112a14a3d409c491fd9a51f5f6eede6d3a3348", "5aee9b21b8e9a4c4975b5f4c4198e6e6e8469df49e2010820e792f393db870f4", + "c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", "55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281", "cae345b5500ef6f84645c816f88b9f7a85a9f3c71697984abdf6849f81e80324", "91e167fa7a256f21f990c526a0a0df840e99aeef0e67dc1f5415b0309486de74", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", + "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", ), ) val dsoGovernance_current = DarResource( "splice-dso-governance-current.dar", - "93645a39701e2ca0fbf1f5744df681ee11f0caed5f1d277a467498bab1ac0de1", + "6666c2be6d6d396900f198b58d34794f0faf1c9a90d595a8408d13298734bcab", PackageMetadata( PackageName.assertFromString("splice-dso-governance"), PackageVersion.assertFromString("0.1.25"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", "54f85ebfc7dfae18f7d70370015dcc6c6792f60135ab369c44ae52c6fc17c274", "62a6fc80fc8ca84dd5f6b33c71f53dd35a02bc01784b8cbc7cbd283eb23863c0", "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", - "f41642b5aa18385bf1ef8ab9272c041be1a3552d623bc0393f2ad03fe4cdf0ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", - "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -3565,24 +3565,20 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", + "b981de244e59400c97d0721622b9554d4b1b58a71a7c535de86f986c5ead0fe9", "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", - "500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6", - "78042a7322f7636af2a65bb0769116d2176317c59503821189e7681e1358cf43", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", + "67967ad0148807e3f5307da9faed89c393fad77595a350b2617fab8bdd02a967", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -3592,9 +3588,13 @@ object DarResources { "275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520", "e5411f3d75f072b944bd88e652112a14a3d409c491fd9a51f5f6eede6d3a3348", "5aee9b21b8e9a4c4975b5f4c4198e6e6e8469df49e2010820e792f393db870f4", + "c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", "55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281", "cae345b5500ef6f84645c816f88b9f7a85a9f3c71697984abdf6849f81e80324", "91e167fa7a256f21f990c526a0a0df840e99aeef0e67dc1f5415b0309486de74", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", + "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", ), ) val dsoGovernance = PackageResource( @@ -4729,13 +4729,15 @@ object DarResources { ) val wallet_0_1_20 = DarResource( "splice-wallet-0.1.20.dar", - "092cab0a1993b39a17c55c3cd7683a920edc8cdb0ec5b7191a5b09530bd25087", + "0d87a1398b5c2973c0d10e05c6cc819edae7768ac7103f11aa93165e13879e2e", PackageMetadata( PackageName.assertFromString("splice-wallet"), PackageVersion.assertFromString("0.1.20"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", @@ -4744,7 +4746,6 @@ object DarResources { "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", - "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -4755,24 +4756,19 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", + "b981de244e59400c97d0721622b9554d4b1b58a71a7c535de86f986c5ead0fe9", "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", - "500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6", - "78042a7322f7636af2a65bb0769116d2176317c59503821189e7681e1358cf43", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -4782,20 +4778,26 @@ object DarResources { "275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520", "e5411f3d75f072b944bd88e652112a14a3d409c491fd9a51f5f6eede6d3a3348", "5aee9b21b8e9a4c4975b5f4c4198e6e6e8469df49e2010820e792f393db870f4", + "c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", "55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281", "cae345b5500ef6f84645c816f88b9f7a85a9f3c71697984abdf6849f81e80324", "91e167fa7a256f21f990c526a0a0df840e99aeef0e67dc1f5415b0309486de74", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", + "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", ), ) val wallet_current = DarResource( "splice-wallet-current.dar", - "092cab0a1993b39a17c55c3cd7683a920edc8cdb0ec5b7191a5b09530bd25087", + "0d87a1398b5c2973c0d10e05c6cc819edae7768ac7103f11aa93165e13879e2e", PackageMetadata( PackageName.assertFromString("splice-wallet"), PackageVersion.assertFromString("0.1.20"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", @@ -4804,7 +4806,6 @@ object DarResources { "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", - "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -4815,24 +4816,19 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", + "b981de244e59400c97d0721622b9554d4b1b58a71a7c535de86f986c5ead0fe9", "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", - "500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6", - "78042a7322f7636af2a65bb0769116d2176317c59503821189e7681e1358cf43", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -4842,9 +4838,13 @@ object DarResources { "275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520", "e5411f3d75f072b944bd88e652112a14a3d409c491fd9a51f5f6eede6d3a3348", "5aee9b21b8e9a4c4975b5f4c4198e6e6e8469df49e2010820e792f393db870f4", + "c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", "55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281", "cae345b5500ef6f84645c816f88b9f7a85a9f3c71697984abdf6849f81e80324", "91e167fa7a256f21f990c526a0a0df840e99aeef0e67dc1f5415b0309486de74", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", + "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", ), ) val wallet = PackageResource( @@ -5798,13 +5798,15 @@ object DarResources { ) val amuletNameService_0_1_20 = DarResource( "splice-amulet-name-service-0.1.20.dar", - "f41642b5aa18385bf1ef8ab9272c041be1a3552d623bc0393f2ad03fe4cdf0ef", + "67967ad0148807e3f5307da9faed89c393fad77595a350b2617fab8bdd02a967", PackageMetadata( PackageName.assertFromString("splice-amulet-name-service"), PackageVersion.assertFromString("0.1.20"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", @@ -5813,7 +5815,6 @@ object DarResources { "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", - "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -5824,24 +5825,19 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", + "b981de244e59400c97d0721622b9554d4b1b58a71a7c535de86f986c5ead0fe9", "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", - "500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6", - "78042a7322f7636af2a65bb0769116d2176317c59503821189e7681e1358cf43", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -5851,20 +5847,26 @@ object DarResources { "275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520", "e5411f3d75f072b944bd88e652112a14a3d409c491fd9a51f5f6eede6d3a3348", "5aee9b21b8e9a4c4975b5f4c4198e6e6e8469df49e2010820e792f393db870f4", + "c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", "55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281", "cae345b5500ef6f84645c816f88b9f7a85a9f3c71697984abdf6849f81e80324", "91e167fa7a256f21f990c526a0a0df840e99aeef0e67dc1f5415b0309486de74", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", + "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", ), ) val amuletNameService_current = DarResource( "splice-amulet-name-service-current.dar", - "f41642b5aa18385bf1ef8ab9272c041be1a3552d623bc0393f2ad03fe4cdf0ef", + "67967ad0148807e3f5307da9faed89c393fad77595a350b2617fab8bdd02a967", PackageMetadata( PackageName.assertFromString("splice-amulet-name-service"), PackageVersion.assertFromString("0.1.20"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", @@ -5873,7 +5875,6 @@ object DarResources { "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", - "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -5884,24 +5885,19 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", + "b981de244e59400c97d0721622b9554d4b1b58a71a7c535de86f986c5ead0fe9", "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", - "500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6", - "78042a7322f7636af2a65bb0769116d2176317c59503821189e7681e1358cf43", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -5911,9 +5907,13 @@ object DarResources { "275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520", "e5411f3d75f072b944bd88e652112a14a3d409c491fd9a51f5f6eede6d3a3348", "5aee9b21b8e9a4c4975b5f4c4198e6e6e8469df49e2010820e792f393db870f4", + "c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", "55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281", "cae345b5500ef6f84645c816f88b9f7a85a9f3c71697984abdf6849f81e80324", "91e167fa7a256f21f990c526a0a0df840e99aeef0e67dc1f5415b0309486de74", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", + "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", ), ) val amuletNameService = PackageResource( @@ -6799,13 +6799,16 @@ object DarResources { ) val walletPayments_0_1_19 = DarResource( "splice-wallet-payments-0.1.19.dar", - "78042a7322f7636af2a65bb0769116d2176317c59503821189e7681e1358cf43", + "b981de244e59400c97d0721622b9554d4b1b58a71a7c535de86f986c5ead0fe9", PackageMetadata( PackageName.assertFromString("splice-wallet-payments"), PackageVersion.assertFromString("0.1.19"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", + "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", @@ -6814,7 +6817,6 @@ object DarResources { "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", - "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -6825,23 +6827,17 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", - "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", - "500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -6851,20 +6847,27 @@ object DarResources { "275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520", "e5411f3d75f072b944bd88e652112a14a3d409c491fd9a51f5f6eede6d3a3348", "5aee9b21b8e9a4c4975b5f4c4198e6e6e8469df49e2010820e792f393db870f4", + "c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", "55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281", "cae345b5500ef6f84645c816f88b9f7a85a9f3c71697984abdf6849f81e80324", "91e167fa7a256f21f990c526a0a0df840e99aeef0e67dc1f5415b0309486de74", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", + "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", ), ) val walletPayments_current = DarResource( "splice-wallet-payments-current.dar", - "78042a7322f7636af2a65bb0769116d2176317c59503821189e7681e1358cf43", + "b981de244e59400c97d0721622b9554d4b1b58a71a7c535de86f986c5ead0fe9", PackageMetadata( PackageName.assertFromString("splice-wallet-payments"), PackageVersion.assertFromString("0.1.19"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", + "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", @@ -6873,7 +6876,6 @@ object DarResources { "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", - "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -6884,23 +6886,17 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", - "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", - "500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -6910,9 +6906,13 @@ object DarResources { "275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520", "e5411f3d75f072b944bd88e652112a14a3d409c491fd9a51f5f6eede6d3a3348", "5aee9b21b8e9a4c4975b5f4c4198e6e6e8469df49e2010820e792f393db870f4", + "c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", "55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281", "cae345b5500ef6f84645c816f88b9f7a85a9f3c71697984abdf6849f81e80324", "91e167fa7a256f21f990c526a0a0df840e99aeef0e67dc1f5415b0309486de74", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", + "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", ), ) val walletPayments = PackageResource( @@ -7865,13 +7865,15 @@ object DarResources { ) val splitwell_0_1_20 = DarResource( "splitwell-0.1.20.dar", - "b4f9a49c7d91f4a352212ddcb4d5f5024e12143ee695cb55e01b2d6ed411c530", + "00064f83b946fa3deea863c0f5cb92d62fefa2b507b3232a747463a2b2e2060b", PackageMetadata( PackageName.assertFromString("splitwell"), PackageVersion.assertFromString("0.1.20"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", @@ -7880,7 +7882,6 @@ object DarResources { "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", - "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -7891,24 +7892,19 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", + "b981de244e59400c97d0721622b9554d4b1b58a71a7c535de86f986c5ead0fe9", "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", - "500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6", - "78042a7322f7636af2a65bb0769116d2176317c59503821189e7681e1358cf43", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -7918,20 +7914,26 @@ object DarResources { "275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520", "e5411f3d75f072b944bd88e652112a14a3d409c491fd9a51f5f6eede6d3a3348", "5aee9b21b8e9a4c4975b5f4c4198e6e6e8469df49e2010820e792f393db870f4", + "c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", "55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281", "cae345b5500ef6f84645c816f88b9f7a85a9f3c71697984abdf6849f81e80324", "91e167fa7a256f21f990c526a0a0df840e99aeef0e67dc1f5415b0309486de74", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", + "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", ), ) val splitwell_current = DarResource( "splitwell-current.dar", - "b4f9a49c7d91f4a352212ddcb4d5f5024e12143ee695cb55e01b2d6ed411c530", + "00064f83b946fa3deea863c0f5cb92d62fefa2b507b3232a747463a2b2e2060b", PackageMetadata( PackageName.assertFromString("splitwell"), PackageVersion.assertFromString("0.1.20"), None, ), Set( + "f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a", + "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", "7adc4c2d07fa3a51173c843cba36e610c1168b2dbbf53076e20c0092eae8763d", "26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674", "4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f", @@ -7940,7 +7942,6 @@ object DarResources { "7cff38e34bd192d498d5a7606aa3b893e35b0f632d582b273f66dab90f2f14ef", "bde4bd30749e99603e5afa354706608601029e225d4983324d617825b634253a", "c280cc3ef501d237efa7b1120ca3ad2d196e089ad596b666bed59a85f3c9a074", - "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", "b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565", "c3bb0c5d04799b3f11bad7c3c102963e115cf53da3e4afcbcfd9f06ebd82b4ff", "0e4a572ab1fb94744abb02243a6bbed6c78fc6e3c8d3f60c655f057692a62816", @@ -7951,24 +7952,19 @@ object DarResources { "93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d", "3b25c9b08ac6d895417c604fc0ee4b7e47ef974ff8fa43f139daa43bb431fefc", "718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b", - "52854220dc199884704958df38befd5492d78384a032fd7558c38f00e3d778a2", - "685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0", - "f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138", - "eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1", + "b981de244e59400c97d0721622b9554d4b1b58a71a7c535de86f986c5ead0fe9", "9e70a8b3510d617f8a136213f33d6a903a10ca0eeec76bb06ba55d1ed9680f69", "d095a2ccf6dd36b2415adc4fa676f9191ba63cd39828dc5207b36892ec350cbc", "6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193", - "500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6", - "78042a7322f7636af2a65bb0769116d2176317c59503821189e7681e1358cf43", - "fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0", + "3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b", "f181cd661f7af3a60bdaae4b0285a2a67beb55d6910fc8431dbae21a5825ec0f", "60c61c542207080e97e378ab447cc355ecc47534b3a3ebbff307c4fb8339bc4d", "fcee8dfc1b81c449b421410edd5041c16ab59c45bbea85bcb094d1b17c3e9df7", "6f8e6085f5769861ae7a40dccd618d6f747297d59b37cab89b93e2fa80b0c024", - "72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be", "fa79192fe1cce03d7d8db36471dde4cf6c96e6d0f07e1c391dd49e355af9b38c", "19f0df5fdaf5a96e137b6ea885fdb378f37bd3166bd9a47ee11518e33fa09a20", "a1fa18133ae48cbb616c4c148e78e661666778c3087d099067c7fe1868cbb3a1", + "a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1", "86d888f34152dae8729900966b44abcb466b9c111699678de58032de601d2b04", "6da1f43a10a179524e840e7288b47bda213339b0552d92e87ae811e52f59fc0e", "3cde94fe9be5c700fc1d9a8ad2277e2c1214609f8c52a5b4db77e466875b8cb7", @@ -7978,9 +7974,13 @@ object DarResources { "275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520", "e5411f3d75f072b944bd88e652112a14a3d409c491fd9a51f5f6eede6d3a3348", "5aee9b21b8e9a4c4975b5f4c4198e6e6e8469df49e2010820e792f393db870f4", + "c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3", + "032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556", "55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281", "cae345b5500ef6f84645c816f88b9f7a85a9f3c71697984abdf6849f81e80324", "91e167fa7a256f21f990c526a0a0df840e99aeef0e67dc1f5415b0309486de74", + "8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191", + "dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea", ), ) val splitwell = PackageResource( diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala index 4b65bf7321..0ffe04b23f 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala @@ -2483,7 +2483,7 @@ object HttpScanAppClient { val args = new allocationv2.SettlementFactory_SettleBatch( choiceArgs.settlement, choiceArgs.transferLegs, - choiceArgs.allocationCids, + choiceArgs.allocations, choiceArgs.actors, new metadatav1.ExtraArgs( choiceContext, diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpTokenStandardAllocationHandler.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpTokenStandardAllocationHandler.scala index 59d41fa8af..a541d9237b 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpTokenStandardAllocationHandler.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpTokenStandardAllocationHandler.scala @@ -138,7 +138,7 @@ class HttpTokenStandardAllocationHandler( // TODO (#4949): this only lists V2 allocations, but it should also do V1 allocations <- contractFetcher.lookupContractsById( amuletallocationv2.AmuletAllocationV2.COMPANION - )(settleBatch.allocationCids.asScala.toSeq) + )(settleBatch.allocations.asScala.toSeq.map(_.allocationCid)) lockedAmulets <- contractFetcher.lookupContractsById(LockedAmulet.COMPANION)( allocations.flatMap(_.payload.lockedAmulet.toScala) ) diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanStore.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanStore.scala index 76e9cf74c3..48229f2c6b 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanStore.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanStore.scala @@ -521,7 +521,7 @@ object ScanStore { ) }, mkFilter(splice.amuletallocationv2.AmuletAllocationV2.COMPANION)(co => - co.payload.allocation.transferLegs.asScala.exists(_.instrumentId.admin == dso) + co.payload.allocation.admin == dso ) { contract => ScanAcsStoreRowData( contract = contract, diff --git a/apps/wallet/frontend/src/__tests__/wallet.test.tsx b/apps/wallet/frontend/src/__tests__/wallet.test.tsx index 9b857fe8cc..a469591545 100644 --- a/apps/wallet/frontend/src/__tests__/wallet.test.tsx +++ b/apps/wallet/frontend/src/__tests__/wallet.test.tsx @@ -55,6 +55,7 @@ import { AmuletAllocationV2 } from '@daml.js/splice-amulet/lib/Splice/AmuletAllo import { AmuletAllocation as AmuletAllocationV1 } from '@daml.js/splice-amulet/lib/Splice/AmuletAllocation'; import { Contract } from '@lfdecentralizedtrust/splice-common-frontend-utils'; import { LockedAmulet } from '@daml.js/splice-amulet/lib/Splice/Amulet'; +import { TransferSide } from '@daml.js/splice-api-token-allocation-v2-1.0.0/lib/Splice/Api/Token/AllocationV2'; const dsoEntry = nameServiceEntries.find(e => e.name.startsWith('dso'))!; @@ -428,14 +429,12 @@ describe('Wallet user can', () => { acceptButtons[0].click(); const calledWithBody = await createPromise; - const acceptableLegs = allocationRequest.transferLegs.filter( - leg => - (leg.sender.owner === alicePartyId || leg.receiver.owner === alicePartyId) && - leg.instrumentId.id === 'Amulet' - ); + const acceptableSides = allocationRequest.allocations + .flatMap(alloc => alloc.transferLegSides) + .filter(side => side.transferLegId === 'acceptable'); const expected = openApiV2RequestFromAllocationRequest( allocationRequest.settlement, - acceptableLegs + acceptableSides ); expect(calledWithBody).toStrictEqual(expected); }); @@ -1452,6 +1451,9 @@ function getAllocationRequestV1() { function getAllocationRequestV2() { return { + originalRequestCid: null, + requestedAt: new Date().toISOString(), + settleAt: new Date().toISOString(), authorizer: { owner: alicePartyId, provider: null, id: '' }, settlement: { executors: ['executor'], @@ -1459,47 +1461,36 @@ function getAllocationRequestV2() { id: 'the_id', cid: null as damlTypes.Optional>, }, - requestedAt: new Date().toISOString(), - settleAt: new Date().toISOString(), settlementDeadline: null as damlTypes.Optional, meta: { values: {} }, }, - transferLegs: [ + allocations: [ { - transferLegId: 'acceptable', - sender: { owner: alicePartyId, provider: null, id: '' }, - receiver: { owner: bobPartyId, provider: null, id: '' }, - amount: '3', - instrumentId: { - id: 'Amulet', - admin: dsoPartyId, - }, - meta: { values: {} }, - }, - { - transferLegId: 'different_sender', - sender: { owner: bobPartyId, provider: null, id: '' }, - receiver: { owner: alicePartyId, provider: null, id: '' }, - amount: '3', - instrumentId: { - id: 'Amulet', - admin: dsoPartyId, - }, - meta: { values: {} }, - }, - { - transferLegId: 'different_instrument', - sender: { owner: alicePartyId, provider: null, id: '' }, - receiver: { owner: bobPartyId, provider: null, id: '' }, - amount: '3', - instrumentId: { - id: 'Another', - admin: dsoPartyId, - }, + admin: dsoPartyId, meta: { values: {} }, + committed: false, + nextIterationFunding: null, + transferLegSides: [ + { + transferLegId: 'acceptable', + instrumentId: 'Amulet', + amount: '3', + meta: { values: {} }, + side: 'SenderSide' as TransferSide, + otherside: { owner: bobPartyId, provider: null, id: '' }, + }, + { + transferLegId: 'different_instrument', + instrumentId: 'Another', + amount: '3', + meta: { values: {} }, + side: 'SenderSide' as TransferSide, + otherside: { owner: bobPartyId, provider: null, id: '' }, + }, + ], }, ], - availableActions: [] as [string[], { tag: string; value: object }[]][], + availableActions: [], meta: { values: {} }, }; } @@ -1514,16 +1505,22 @@ function getAllocationV2( return { lockedAmulet: null as damlTypes.Optional>, dso: dsoPartyId, + createdAt: new Date().toISOString(), expiresAt: new Date().toISOString(), + numIterations: '0', allocation: { - transferLegs: [ + admin: dsoPartyId, + meta: { values: {} }, + committed: false, + nextIterationFunding: null, + transferLegSides: [ { transferLegId, - sender: { owner: alicePartyId, provider: null, id: '' }, - receiver: { owner: receiver, provider: null, id: '' }, - amount, + side: 'SenderSide', + instrumentId: 'Amulet', meta: { values: {} }, - instrumentId: { id: 'Amulet', admin: dsoPartyId }, + otherside: { owner: receiver, provider: null, id: '' }, + amount, }, ], settlement: { @@ -1532,8 +1529,6 @@ function getAllocationV2( id: settlementId, cid: null, }, - requestedAt: new Date().toISOString(), - settleAt: new Date().toISOString(), settlementDeadline: null, meta: { values: {} }, }, diff --git a/apps/wallet/frontend/src/components/AllocationSettlementDisplay.tsx b/apps/wallet/frontend/src/components/AllocationSettlementDisplay.tsx index 150a80ea33..8fc9199292 100644 --- a/apps/wallet/frontend/src/components/AllocationSettlementDisplay.tsx +++ b/apps/wallet/frontend/src/components/AllocationSettlementDisplay.tsx @@ -11,14 +11,7 @@ import { SettlementInfo } from '@daml.js/splice-api-token-allocation-v2/lib/Spli const AllocationSettlementDisplay: React.FC<{ settlement: SettlementInfo; }> = ({ settlement }) => { - const { - settleAt, - requestedAt, - settlementDeadline, - settlementRef, - executors, - meta: settlementMeta, - } = settlement; + const { settlementDeadline, settlementRef, executors, meta: settlementMeta } = settlement; return ( @@ -46,12 +39,6 @@ const AllocationSettlementDisplay: React.FC<{ ))} - - Requested at: - - - Settle at: - {settlementDeadline ? ( Settlement deadline:{' '} diff --git a/apps/wallet/frontend/src/components/CreateAllocation.tsx b/apps/wallet/frontend/src/components/CreateAllocation.tsx index fbce251a2a..0bcae93342 100644 --- a/apps/wallet/frontend/src/components/CreateAllocation.tsx +++ b/apps/wallet/frontend/src/components/CreateAllocation.tsx @@ -6,7 +6,7 @@ import { useMutation } from '@tanstack/react-query'; import { AllocateAmuletV2Request, AllocateAmuletRequestSettlementSettlementRef, - TransferLegV2, + TransferLegSide, } from '@lfdecentralizedtrust/wallet-openapi'; import { Alert, @@ -27,12 +27,14 @@ import { damlTimestampToOpenApiTimestamp, isValidDamlTimestamp, } from '../utils/timestampConversion'; +import { usePrimaryParty } from '../hooks'; const CreateAllocation: React.FC = () => { const { createAllocationV2 } = useWalletClient(); + const userParty = usePrimaryParty(); const [error, setError] = useState(null); const [allocation, setAllocation] = useState(emptyForm()); - const validated = validatedForm(allocation); + const validated = validatedForm(userParty!, allocation); const createAllocationMutation = useMutation({ mutationFn: async () => { return validated && (await createAllocationV2(validated)); @@ -174,32 +176,6 @@ const CreateAllocation: React.FC = () => { - Requested at ({DAML_TIMESTAMP_FORMAT}) - - setAllocation({ - ...allocation, - settlement: { ...allocation.settlement, requested_at: event.target.value }, - }) - } - /> - Settle at ({DAML_TIMESTAMP_FORMAT}) - - setAllocation({ - ...allocation, - settlement: { ...allocation.settlement, settle_at: event.target.value }, - }) - } - /> Settlement deadline (optional, {DAML_TIMESTAMP_FORMAT}) @@ -317,8 +293,6 @@ interface PartialAllocateAmuletV2Request { settlement: { executors: string[]; settlement_ref: AllocateAmuletRequestSettlementSettlementRef; - requested_at: string; - settle_at: string; settlement_deadline?: string; }; transfer_legs: PartialTransferLeg[]; @@ -332,8 +306,6 @@ function emptyForm(): PartialAllocateAmuletV2Request { return { settlement: { executors: [''], - requested_at: '', - settle_at: '', settlement_deadline: undefined, settlement_ref: { id: '', cid: undefined }, }, @@ -341,33 +313,45 @@ function emptyForm(): PartialAllocateAmuletV2Request { }; } -function validatedForm(partial: PartialAllocateAmuletV2Request): AllocateAmuletV2Request | null { +function validatedForm( + userParty: string, + partial: PartialAllocateAmuletV2Request +): AllocateAmuletV2Request | null { if ( !partial.settlement.executors.length || partial.settlement.executors.some(e => !e) || !partial.settlement.settlement_ref?.id || - ![partial.settlement.requested_at, partial.settlement.settle_at].every(isValidDamlTimestamp) || (partial.settlement.settlement_deadline && !isValidDamlTimestamp(partial.settlement.settlement_deadline)) ) { return null; } - const validLegs: TransferLegV2[] = []; + const validLegSides: TransferLegSide[] = []; for (const leg of partial.transfer_legs) { if (!leg.transfer_leg_id || !leg.sender || !leg.receiver || !leg.amount) return null; - validLegs.push({ + let side: 'SENDERSIDE' | 'RECEIVERSIDE'; + let otherside: string; + if (userParty === leg.sender) { + side = 'SENDERSIDE'; + otherside = leg.receiver; + } else if (userParty === leg.receiver) { + side = 'RECEIVERSIDE'; + otherside = leg.sender; + } else { + return null; + } + validLegSides.push({ transfer_leg_id: leg.transfer_leg_id, - sender: leg.sender, - receiver: leg.receiver, + side, + otherside, + meta: {}, amount: leg.amount, }); } - if (validLegs.length === 0) return null; + if (validLegSides.length === 0) return null; return { settlement: { executors: partial.settlement.executors, - requested_at: damlTimestampToOpenApiTimestamp(partial.settlement.requested_at), - settle_at: damlTimestampToOpenApiTimestamp(partial.settlement.settle_at), settlement_deadline: partial.settlement.settlement_deadline ? damlTimestampToOpenApiTimestamp(partial.settlement.settlement_deadline) : undefined, @@ -376,6 +360,10 @@ function validatedForm(partial: PartialAllocateAmuletV2Request): AllocateAmuletV cid: partial.settlement.settlement_ref.cid, }, }, - transfer_legs: validLegs, + transfer_leg_sides: validLegSides, + // TODO (#5498): make the FE specify these + committed: false, + meta: {}, + next_iteration_funding: undefined, }; } diff --git a/apps/wallet/frontend/src/components/ListAllocationRequests.tsx b/apps/wallet/frontend/src/components/ListAllocationRequests.tsx index 068b994722..05222b2fcb 100644 --- a/apps/wallet/frontend/src/components/ListAllocationRequests.tsx +++ b/apps/wallet/frontend/src/components/ListAllocationRequests.tsx @@ -31,11 +31,13 @@ import { import { SettlementInfo, TransferLeg, + TransferLegSide, } from '@daml.js/splice-api-token-allocation-v2/lib/Splice/Api/Token/AllocationV2/module'; import { damlTimestampToOpenApiTimestamp } from '../utils/timestampConversion'; import AllocationSettlementDisplay from './AllocationSettlementDisplay'; import UseGetAmuletRules from '../hooks/scan-proxy/useGetAmuletRules'; import { ContractId } from '@daml/types'; +import { transferLegSidesToTransferLegs } from '../utils/tokenStandard'; dayjs.extend(relativeTime); @@ -99,8 +101,8 @@ const AllocationRequestDisplay: React.FC<{ const payload = request.payload; const isV2 = isV2AllocationRequest(payload); const { settlement, transferLegs } = isV2 - ? { settlement: payload.settlement, transferLegs: payload.transferLegs } - : v1RequestToV2Display(payload); + ? v2RequestToDisplay(payload) + : v1RequestToDisplay(payload); const requestMeta = payload.meta; const { rejectAllocationRequest } = useWalletClient(); @@ -189,18 +191,16 @@ const V2AllocationRequestActionButton: React.FC<{ dso: string; }> = ({ allocationRequest, userParty, allocations, dso }) => { const payload = allocationRequest.payload; - const amuletLegsForUser = payload.transferLegs.filter( - leg => - (leg.sender.owner === userParty || leg.receiver.owner === userParty) && - leg.instrumentId.id === 'Amulet' && - leg.instrumentId.admin === dso - ); + const amuletLegSidesForUser = payload.allocations + .filter(allocation => allocation.admin === dso) + .flatMap(allocation => allocation.transferLegSides) + .filter(side => side.instrumentId === 'Amulet'); const isAuthorizer = payload.authorizer.owner === userParty && (payload.authorizer.provider === null || payload.authorizer.provider === undefined) && payload.authorizer.id === ''; // basicAccount check: authorizer matches basicAccount(userParty) - const canAccept = amuletLegsForUser.length > 0 && isAuthorizer; + const canAccept = amuletLegSidesForUser.length > 0 && isAuthorizer; const correspondingAllocation = allocations.find(alloc => isAllocationForRequest(alloc, allocationRequest) @@ -211,7 +211,7 @@ const V2AllocationRequestActionButton: React.FC<{ const { createAllocationV2, withdrawAllocationV2 } = useWalletClient(); const createAllocationV2Mutation = useMutation({ mutationFn: async () => { - const req = openApiV2RequestFromAllocationRequest(payload.settlement, amuletLegsForUser); + const req = openApiV2RequestFromAllocationRequest(payload.settlement, amuletLegSidesForUser); return await createAllocationV2(req); }, onSuccess: () => {}, @@ -396,7 +396,9 @@ function isAllocationForTransferLeg( sameExecutor = allocation.payload.allocation.settlement.executors.some( e => e === allocationRequest.payload.settlement.executor ); - sameLegId = allocation.payload.allocation.transferLegs.some(leg => leg.transferLegId === legId); + sameLegId = allocation.payload.allocation.transferLegSides.some( + side => side.transferLegId === legId + ); } else { sameExecutor = allocation.payload.allocation.settlement.executor === @@ -443,7 +445,7 @@ export function openApiV1RequestFromTransferLeg( /** V2: build AllocateAmuletV2Request from settlement + filtered transfer legs */ export function openApiV2RequestFromAllocationRequest( settlement: SettlementInfo, - transferLegs: TransferLeg[] + transferLegSides: TransferLegSide[] ): AllocateAmuletV2Request { return { settlement: { @@ -452,34 +454,46 @@ export function openApiV2RequestFromAllocationRequest( id: settlement.settlementRef.id, cid: settlement.settlementRef.cid as string, }, - requested_at: damlTimestampToOpenApiTimestamp(settlement.requestedAt), - settle_at: damlTimestampToOpenApiTimestamp(settlement.settleAt), meta: settlement.meta.values, ...(settlement.settlementDeadline ? { settlement_deadline: damlTimestampToOpenApiTimestamp(settlement.settlementDeadline) } : {}), }, - transfer_legs: transferLegs.map(leg => ({ - transfer_leg_id: leg.transferLegId, - sender: leg.sender.owner, - receiver: leg.receiver.owner, - amount: leg.amount, - meta: leg.meta.values, + transfer_leg_sides: transferLegSides.map(side => ({ + transfer_leg_id: side.transferLegId, + amount: side.amount, + otherside: side.otherside.owner, + side: side.side === 'SenderSide' ? 'SENDERSIDE' : 'RECEIVERSIDE', + meta: side.meta.values, })), + // TODO (#5498): make the FE specify these + committed: false, + next_iteration_funding: {}, + meta: {}, }; } -/** Convert V1 AllocationRequest fields to V2 shapes for display */ -function v1RequestToV2Display(payload: AllocationRequestV1): { +type DisplayRequest = { settlement: SettlementInfo; transferLegs: TransferLeg[]; -} { +}; +function v2RequestToDisplay(payload: AllocationRequestV2): DisplayRequest { + const transferLegs = transferLegSidesToTransferLegs( + payload.authorizer, + payload.allocations.flatMap(allocation => allocation.transferLegSides) + ); + return { + settlement: payload.settlement, + transferLegs, + }; +} + +/** Convert V1 AllocationRequest fields to V2 shapes for display */ +function v1RequestToDisplay(payload: AllocationRequestV1): DisplayRequest { return { settlement: { executors: [payload.settlement.executor], settlementRef: payload.settlement.settlementRef, - requestedAt: payload.settlement.requestedAt, - settleAt: payload.settlement.settleBefore, settlementDeadline: null, meta: payload.settlement.meta, }, @@ -488,7 +502,7 @@ function v1RequestToV2Display(payload: AllocationRequestV1): { sender: { owner: leg.sender, provider: null, id: '' }, receiver: { owner: leg.receiver, provider: null, id: '' }, amount: leg.amount, - instrumentId: leg.instrumentId, + instrumentId: leg.instrumentId.id, meta: leg.meta, })), }; diff --git a/apps/wallet/frontend/src/components/ListAllocations.tsx b/apps/wallet/frontend/src/components/ListAllocations.tsx index 6d5553bae5..bcd3b0c027 100644 --- a/apps/wallet/frontend/src/components/ListAllocations.tsx +++ b/apps/wallet/frontend/src/components/ListAllocations.tsx @@ -18,11 +18,14 @@ import { } from '../contexts/WalletServiceContext'; import { AllocationSpecification } from '@daml.js/splice-api-token-allocation-v2/lib/Splice/Api/Token/AllocationV2/module'; import { ContractId } from '@daml/types'; +import { transferLegSidesToTransferLegs } from '../utils/tokenStandard'; +import { usePrimaryParty } from '../hooks'; const ListAllocations: React.FC = () => { + const primaryPartyId = usePrimaryParty(); const allocationsQuery = useAmuletAllocations(); - if (allocationsQuery.isLoading) { + if (allocationsQuery.isLoading || !primaryPartyId) { return ; } if (allocationsQuery.isError) { @@ -47,19 +50,26 @@ const ListAllocations: React.FC = () => { Allocations {allocations.map(allocation => ( - + ))} ); }; const AllocationDisplay: React.FC<{ - allocation: Contract; -}> = ({ allocation }) => { + userParty: string; + allocation: Contract; +}> = ({ userParty, allocation }) => { const { withdrawAllocation, withdrawAllocationV2 } = useWalletClient(); - const v2 = isV2Allocation(allocation.payload); - const spec = getAllocationSpec(allocation.payload); - const { settlement, transferLegs } = spec; + const allocationPayload = allocation.payload; + const v2 = isV2Allocation(allocationPayload); + const spec = getAllocationSpec(userParty, allocationPayload); + const { settlement, transferLegSides } = spec; + const transferLegs = transferLegSidesToTransferLegs(spec.authorizer, transferLegSides); return ( - {BigNumber(amount).toFormat()} {instrumentId.id} + {BigNumber(amount).toFormat()} {instrumentId} diff --git a/apps/wallet/frontend/src/contexts/WalletServiceContext.tsx b/apps/wallet/frontend/src/contexts/WalletServiceContext.tsx index 505e91f415..3ec40a24e8 100644 --- a/apps/wallet/frontend/src/contexts/WalletServiceContext.tsx +++ b/apps/wallet/frontend/src/contexts/WalletServiceContext.tsx @@ -392,9 +392,14 @@ export const WalletClientProvider: React.FC const res = await walletClient.listAllocationRequests(); return res.allocation_requests.map(ar => { try { - return Contract.decodeOpenAPI(ar.contract, AllocationRequestV2); - } catch { - return Contract.decodeOpenAPI(ar.contract, AllocationRequestV1); + try { + return Contract.decodeOpenAPI(ar.contract, AllocationRequestV2); + } catch { + return Contract.decodeOpenAPI(ar.contract, AllocationRequestV1); + } + } catch (e) { + console.error('Unsupported AllocationRequest', e, ar); + throw e; } }); }, diff --git a/apps/wallet/frontend/src/utils/tokenStandard.ts b/apps/wallet/frontend/src/utils/tokenStandard.ts new file mode 100644 index 0000000000..8cfc2c97dc --- /dev/null +++ b/apps/wallet/frontend/src/utils/tokenStandard.ts @@ -0,0 +1,30 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import { + TransferLeg, + TransferLegSide, +} from '@daml.js/splice-api-token-allocation-v2-1.0.0/lib/Splice/Api/Token/AllocationV2'; +import { Account } from '@daml.js/splice-api-token-holding-v2-1.0.0/lib/Splice/Api/Token/HoldingV2'; + +export function transferLegSidesToTransferLegs( + authorizer: Account, + sides: TransferLegSide[] +): TransferLeg[] { + return sides.map(side => { + const sender: Account = + side.side === 'SenderSide' ? authorizer : basicAccount(side.otherside.owner); + const receiver = side.side === 'ReceiverSide' ? authorizer : basicAccount(side.otherside.owner); + return { + transferLegId: side.transferLegId, + sender, + receiver, + amount: side.amount, + instrumentId: side.instrumentId, + meta: side.meta, + }; + }); +} + +export function basicAccount(party: string): Account { + return { owner: party, provider: null, id: '' }; +} diff --git a/apps/wallet/src/main/openapi/wallet-internal.yaml b/apps/wallet/src/main/openapi/wallet-internal.yaml index c74904c492..cad6344105 100644 --- a/apps/wallet/src/main/openapi/wallet-internal.yaml +++ b/apps/wallet/src/main/openapi/wallet-internal.yaml @@ -1977,15 +1977,14 @@ components: type: object required: - settlement - - transfer_legs + - transfer_leg_sides + - committed properties: settlement: type: object required: - executors - settlement_ref - - requested_at - - settle_at properties: executors: type: array @@ -2000,12 +1999,6 @@ components: type: string cid: type: string - requested_at: - type: integer - format: int64 - settle_at: - type: integer - format: int64 settlement_deadline: type: integer format: int64 @@ -2013,24 +2006,38 @@ components: type: object additionalProperties: type: string - transfer_legs: + transfer_leg_sides: type: array items: - $ref: "#/components/schemas/TransferLegV2" + $ref: "#/components/schemas/TransferLegSide" + next_iteration_funding: + type: object + additionalProperties: + description: "BigDecimal amount, encoded as string to prevent precision loss" + type: string + committed: + type: boolean + default: false + meta: + type: object + additionalProperties: + type: string - TransferLegV2: + TransferLegSide: type: object required: - transfer_leg_id - - sender - - receiver + - side + - otherside - amount properties: transfer_leg_id: type: string - sender: + side: type: string - receiver: + enum: [ SENDERSIDE, RECEIVERSIDE ] + otherside: + description: A party id type: string amount: type: string diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/api/client/commands/HttpWalletAppClient.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/api/client/commands/HttpWalletAppClient.scala index da351401ad..a4941f374e 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/api/client/commands/HttpWalletAppClient.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/api/client/commands/HttpWalletAppClient.scala @@ -1241,7 +1241,7 @@ object HttpWalletAppClient { ): EitherT[Future, Either[ Throwable, HttpResponse, - ], http.AllocateAmuletV2Response] = + ], http.AllocateAmuletV2Response] = { client.allocateAmuletV2( definitions.AllocateAmuletV2Request( definitions.AllocateAmuletV2Request.Settlement( @@ -1250,26 +1250,33 @@ object HttpWalletAppClient { spec.settlement.settlementRef.id, spec.settlement.settlementRef.cid.map(_.contractId).toScala, ), - requestedAt = - Codec.encode(CantonTimestamp.assertFromInstant(spec.settlement.requestedAt)), - settleAt = Codec.encode(CantonTimestamp.assertFromInstant(spec.settlement.settleAt)), settlementDeadline = spec.settlement.settlementDeadline .map(deadline => Codec.encode(CantonTimestamp.assertFromInstant(deadline))) .toScala, meta = Some(spec.settlement.meta.values.asScala.toMap), ), - spec.transferLegs.asScala.map { transferLegs => - definitions.TransferLegV2( - transferLegs.transferLegId, - sender = transferLegs.sender.owner, - receiver = transferLegs.receiver.owner, - Codec.JavaBigDecimal.instance.encode(transferLegs.amount), - Some(transferLegs.meta.values.asScala.toMap), + spec.transferLegSides.asScala.map { transferLegSide => + definitions.TransferLegSide( + transferLegSide.transferLegId, + side = transferLegSide.side match { + case allocationv2.TransferSide.SENDERSIDE => + definitions.TransferLegSide.Side.Senderside + case allocationv2.TransferSide.RECEIVERSIDE => + definitions.TransferLegSide.Side.Receiverside + }, + otherside = transferLegSide.otherside.owner, + Codec.JavaBigDecimal.instance.encode(transferLegSide.amount), + Some(transferLegSide.meta.values.asScala.toMap), ) }.toVector, + nextIterationFunding = spec.nextIterationFunding.toScala + .map(_.asScala.toMap.view.mapValues(Codec.JavaBigDecimal.instance.encode).toMap), + committed = spec.committed, + meta = Some(spec.meta.values.asScala.toMap), ), headers = headers, ) + } override protected def handleOk()(implicit decoder: TemplateJsonDecoder diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandler.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandler.scala index 96c8032149..b14bd77814 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandler.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandler.scala @@ -50,7 +50,7 @@ import org.lfdecentralizedtrust.splice.util.{ import org.lfdecentralizedtrust.splice.wallet.{UserWalletManager, UserWalletService} import org.lfdecentralizedtrust.splice.wallet.store.{TxLogEntry, UserWalletStore} import org.lfdecentralizedtrust.splice.wallet.treasury.TreasuryService -import TreasuryService.AmuletOperationDedupConfig +import TreasuryService.{AmuletOperationDedupConfig, basicAccount} import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.transferpreapproval.TransferPreapprovalProposal import org.lfdecentralizedtrust.splice.wallet.util.{TopupUtil, ValidatorTopupConfig} import com.digitalasset.canton.logging.{ErrorLoggingContext, NamedLoggerFactory} @@ -73,7 +73,6 @@ import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.{ allocationv1, allocationv2, holdingv1, - holdingv2, metadatav1, transferinstructionv1, transferinstructionv2, @@ -1187,6 +1186,7 @@ class HttpWalletHandler( withSpan(s"$workflowId.allocateAmuletV2") { _ => _ => val now = walletManager.clock.now.toInstant val authorizer = userWallet.store.key.endUserParty + val authorizerAccount = TreasuryService.basicAccount(authorizer) val commandId = CommandId( "org.lfdecentralizedtrust.splice.wallet.allocateAmulet", Seq(authorizer), @@ -1195,6 +1195,28 @@ class HttpWalletHandler( body.settlement.settlementRef.cid.getOrElse(""), ) ++ body.settlement.executors, ) + val transferLegSides = body.transferLegSides.map { side => + new allocationv2.TransferLegSide( + side.transferLegId, + side.side match { + case d0.TransferLegSide.Side.Receiverside => + allocationv2.TransferSide.RECEIVERSIDE + case d0.TransferLegSide.Side.Senderside => + allocationv2.TransferSide.SENDERSIDE + case other => + throw Status.INVALID_ARGUMENT + .withDescription( + s"$other is not a valid TransferLegSide. Must be one of ${d0.TransferLegSide.Side.values + .map(_.value)}" + ) + .asRuntimeException() + }, + basicAccount(side.otherside), + Codec.tryDecode(Codec.JavaBigDecimal)(side.amount), + "Amulet", + new metadatav1.Metadata(side.meta.getOrElse(Map.empty).asJava), + ) + } val specification = new allocationv2.AllocationSpecification( new allocationv2.SettlementInfo( body.settlement.executors.asJava, @@ -1202,25 +1224,22 @@ class HttpWalletHandler( body.settlement.settlementRef.id, body.settlement.settlementRef.cid.map(cid => new AnyContract.ContractId(cid)).toJava, ), - Codec.tryDecode(Codec.Timestamp)(body.settlement.requestedAt).toInstant, - Codec.tryDecode(Codec.Timestamp)(body.settlement.settleAt).toInstant, body.settlement.settlementDeadline .map(Codec.tryDecode(Codec.Timestamp)) .map(_.toInstant) .toJava, new metadatav1.Metadata(body.settlement.meta.getOrElse(Map.empty).asJava), ), - body.transferLegs.map { leg => - new allocationv2.TransferLeg( - leg.transferLegId, - TreasuryService.basicAccount(leg.sender), - TreasuryService.basicAccount(leg.receiver), - Codec.tryDecode(Codec.JavaBigDecimal)(leg.amount), - new holdingv2.InstrumentId(userWallet.store.key.dsoParty.toProtoPrimitive, "Amulet"), - new metadatav1.Metadata(leg.meta.getOrElse(Map.empty).asJava), - ) - }.asJava, - /*authorizer=*/ TreasuryService.basicAccount(authorizer), + userWallet.store.key.dsoParty.toProtoPrimitive, + authorizerAccount, + transferLegSides.asJava, + body.nextIterationFunding + .map(_.map { case (instrument, amount) => + instrument -> Codec.tryDecode(Codec.BigDecimal)(amount).bigDecimal + }.asJava) + .toJava, + body.committed, + new metadatav1.Metadata(body.meta.getOrElse(Map.empty).asJava), ) val dedupConfig = AmuletOperationDedupConfig( commandId, diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/store/UserWalletStore.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/store/UserWalletStore.scala index e51584b675..7f4ea075a4 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/store/UserWalletStore.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/store/UserWalletStore.scala @@ -807,9 +807,8 @@ object UserWalletStore { UserWalletAcsStoreRowData(contract) }, mkFilter(amuletallocationv2.AmuletAllocationV2.COMPANION) { co => - co.payload.allocation.transferLegs.asScala.exists { transferLeg => - transferLeg.instrumentId.admin == dso && transferLeg.sender.owner == endUser - } + co.payload.allocation.admin == dso && + co.payload.allocation.authorizer.owner == endUser } { contract => UserWalletAcsStoreRowData(contract) }, @@ -845,9 +844,8 @@ object UserWalletStore { } )(contract => UserWalletAcsInterfaceViewRowData(contract)), mkFilterInterface(allocationrequestv2.AllocationRequest.INTERFACE)(co => - co.payload.transferLegs.asScala.exists { transferLeg => - transferLeg.instrumentId.admin == dso && (transferLeg.sender.owner == endUser || transferLeg.receiver.owner == endUser) - } + co.payload.authorizer.owner == endUser && + co.payload.allocations.asScala.exists(_.admin == dso) )(contract => UserWalletAcsInterfaceViewRowData(contract)), ), ) diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/store/UserWalletTxLogParser.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/store/UserWalletTxLogParser.scala index 364ad4be03..6ec78c09c1 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/store/UserWalletTxLogParser.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/store/UserWalletTxLogParser.scala @@ -110,22 +110,16 @@ import org.lfdecentralizedtrust.splice.wallet.store.TxLogEntry.{ TransferTransactionSubtype, } import com.digitalasset.canton.topology.{PartyId, SynchronizerId} -import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.holdingv2 class UserWalletTxLogParser( override val loggerFactory: NamedLoggerFactory, endUserParty: PartyId, - dsoParty: PartyId, ) extends TxLogStore.Parser[TxLogEntry] with NamedLogging { import UserWalletTxLogParser.* private val endUserPartyProtoPrimitive = endUserParty.toProtoPrimitive - // TODO (#4973): remove this if/when v1 and v2 InstrumentId are unified - private val amuletInstrumentIdV2 = new holdingv2.InstrumentId( - dsoParty.toProtoPrimitive, - "Amulet", - ) + private val amuletInstrumentIdName = "Amulet" // ignoreUnexpectedAmuletCreateArchive disables the warning when we // hit a bare create/archive of an amulet contract. We use this for @@ -1110,7 +1104,7 @@ class UserWalletTxLogParser( node.argument.value.transferLegs.asScala.foldLeft(State.empty) { case (stateAcc, transferLeg) => if ( - transferLeg.instrumentId == amuletInstrumentIdV2 && (transferLeg.receiver.owner == endUserPartyProtoPrimitive || transferLeg.sender.owner == endUserPartyProtoPrimitive) + transferLeg.instrumentId == amuletInstrumentIdName && (transferLeg.receiver.owner == endUserPartyProtoPrimitive || transferLeg.sender.owner == endUserPartyProtoPrimitive) ) { stateAcc.appended( State( diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/store/db/DbUserWalletStore.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/store/db/DbUserWalletStore.scala index 5ddce8269f..ad61ec1beb 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/store/db/DbUserWalletStore.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/store/db/DbUserWalletStore.scala @@ -64,7 +64,6 @@ class DbUserWalletTxLogStoreConfig(loggerFactory: NamedLoggerFactory, key: UserW new UserWalletTxLogParser( loggerFactory, endUserParty = key.endUserParty, - dsoParty = key.dsoParty, ) override def entryToRow: org.lfdecentralizedtrust.splice.wallet.store.TxLogEntry => Option[ org.lfdecentralizedtrust.splice.wallet.store.db.WalletTables.UserWalletTxLogStoreRowData diff --git a/daml/dars.lock b/daml/dars.lock index 1899b37305..cc71d4a87e 100644 --- a/daml/dars.lock +++ b/daml/dars.lock @@ -9,7 +9,7 @@ splice-amulet 0.1.15 67fac2f853bce8dbf0b9817bb5ba7c59f10e8120b7c808696f7010e5f0c splice-amulet 0.1.16 c208d7ead1e4e9b610fc2054d0bf00716144ad444011bce0b02dcd6cd0cb8a23 splice-amulet 0.1.17 6c5802f86709a0ad4784af81f0bab40f3070b2f58128d8843da1e1784c147802 splice-amulet 0.1.18 a31be0483f3175647053f28965a4e6d97e3dbc433ea2338be303fae69bbcff6a -splice-amulet 0.1.19 500ecfbf1676088d2e2080cdb60bfff0583dc3400fa04a1c10b90b864b1d66d6 +splice-amulet 0.1.19 c6332da5f387f7f526124b857867b00fcaaee0414954926a77b7d216741de7b3 splice-amulet 0.1.2 1446ffdf23326cef2de97923df96618eb615792bea36cf1431f03639448f1645 splice-amulet 0.1.3 0d89016d5a90eb8bced48bbac99e81c57781b3a36094b8d48b8e4389851e19af splice-amulet 0.1.4 a36ef8888fb44caae13d96341ce1fabd84fc9e2e7b209bbc3caabb48b6be1668 @@ -31,7 +31,7 @@ splice-amulet-name-service 0.1.17 bcc80dce253c7b89efd9b263be5260a9609f8cb1fb5ea6 splice-amulet-name-service 0.1.18 64232089d6dc6ae1eabcebcbe5e8b1aa8f413e9c57e8986d2bca883cc306fde2 splice-amulet-name-service 0.1.19 7b784f7f03d3e035ea5d828ab64a476e7e0cbfffaec393619598f13e54d18013 splice-amulet-name-service 0.1.2 711a2974d65e6ebd149704da75f3f71234798687ab895b92f066c865dbdeeabb -splice-amulet-name-service 0.1.20 f41642b5aa18385bf1ef8ab9272c041be1a3552d623bc0393f2ad03fe4cdf0ef +splice-amulet-name-service 0.1.20 67967ad0148807e3f5307da9faed89c393fad77595a350b2617fab8bdd02a967 splice-amulet-name-service 0.1.3 beb4b85f3f0cf36dfb93fc917d3ac218ee5d41b6e70604720cb228d85e168ee0 splice-amulet-name-service 0.1.4 053c7f4c2a77312e7d465a4fa7dc8cb298754ad12c0c987a7c401bd724e65efc splice-amulet-name-service 0.1.5 6188c8b5f612278f988fc95c11e9742993ad3ac6ad0809f9af06ee9d366dc4a8 @@ -39,21 +39,21 @@ splice-amulet-name-service 0.1.6 a208aab2c4a248ab2eff352bd382f8b3bbadc92464123db splice-amulet-name-service 0.1.7 ba7806d9b2d593eac74a050161c54ae1325d170bf175cb66a9c1e5e5ffb88c3d splice-amulet-name-service 0.1.8 efeb3f9b2b92e55fac4ec2d6164f95407a01477240c7465e576df4e310f54bd3 splice-amulet-name-service 0.1.9 f1b5915ad45ded616f43f83c735b7ee158b5eb58abe758a721e50eee19b3e531 -splice-amulet-name-service-test 0.1.24 86231bafcd4b85c71107d48f821efc1b46ac628888dad54df1f08b0ae9c358a9 -splice-amulet-test 0.1.23 36a2c9ad8de4f924e9d1530a369982994daf31125bd276ad755e455852774df7 +splice-amulet-name-service-test 0.1.24 d52368df6fd662cb8d5790541de976e25d6fc1e95626dfd9ef4492c95aba2f98 +splice-amulet-test 0.1.23 5d4234c11972c95dc64e662d4f4fadc255d8ae6b2bbde77c738e786de41bd3da splice-api-featured-app-v1 1.0.0 7804375fe5e4c6d5afe067bd314c42fe0b7d005a1300019c73154dd939da4dda splice-api-featured-app-v2 1.0.0 dd22e3e168a8c7fd0313171922dabf1f7a3b131bd9bfc9ff98e606f8c57707ea splice-api-token-allocation-instruction-v1 1.0.0 275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520 -splice-api-token-allocation-instruction-v2 1.0.0 fb5edc9e67cff9965ffabec870cce201a85f8b6c3c198c1eb6cc50153e9b45f0 +splice-api-token-allocation-instruction-v2 1.0.0 a999f5ca38421121668942ed39443110b4b17ef84667ec919116b4862e178db1 splice-api-token-allocation-request-v1 1.0.0 6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193 -splice-api-token-allocation-request-v2 1.0.0 685416afcf687894882f7b72decda84c9105f606178dd5e9fea8f11acda06cb0 +splice-api-token-allocation-request-v2 1.0.0 3930dfbebb52da0bb76d4a79b0c80e736547ebb1313af6900c97061f0191e68b splice-api-token-allocation-v1 1.0.0 93c942ae2b4c2ba674fb152fe38473c507bda4e82b4e4c5da55a552a9d8cce1d -splice-api-token-allocation-v2 1.0.0 f72e17d3816dbcea413f23fa7127b318773c60bf58fdce0552df03968c720138 +splice-api-token-allocation-v2 1.0.0 f3b45f29d345f4eac17cb7a4cb403be50c8620fb213c70d2d121637f33b88b9a splice-api-token-burn-mint-v1 1.0.0 9cc2cbc838ef38dc2c7f34014c9c452bcf71b8e2a4f939235fc0b5d0924b185e splice-api-token-holding-v1 1.0.0 718a0f77e505a8de22f188bd4c87fe74101274e9d4cb1bfac7d09aec7158d35b splice-api-token-holding-v2 1.0.0 b2e23c1a42a66d3286a2b8a8df3fad8db99d580a330ed3d871b58953dfd42565 splice-api-token-metadata-v1 1.0.0 4ded6b668cb3b64f7a88a30874cd41c75829f5e064b3fbbadf41ec7e8363354f -splice-api-token-transfer-events-v2 1.0.0 eef21cbd76e205fcd6a5a928332dbb2bc5a80c6eb294ed30b6eeddbc9c2d73c1 +splice-api-token-transfer-events-v2 1.0.0 032a37124a5a9b0b504f743491e92d7c98dbdfc0f0d44313e0c7d431750a1556 splice-api-token-transfer-instruction-v1 1.0.0 55ba4deb0ad4662c4168b39859738a0e91388d252286480c7331b3f71a517281 splice-api-token-transfer-instruction-v2 1.0.0 26d24a66890fe483ba3b67d0f09d939f36dde4d1f2005e971149f254735e7674 splice-app-manager 0.1.0 e3605e0112595187ec963700d28f17fe042f3b80c862363e299003b9423a1844 @@ -76,7 +76,7 @@ splice-dso-governance 0.1.21 2d306cfe8cdb3daf2d21f84dfecc3e2f26a41504e58fe25cb7f splice-dso-governance 0.1.22 5c28530209b9ab37c5f187132cd826709bb18b0efe28411488ab750870414738 splice-dso-governance 0.1.23 0c94a036ac5168a1dee26b435838e062f0d2f47d6eac49303978228ae559edb9 splice-dso-governance 0.1.24 4974c654485d4ecaa6b5caf8ef3c2679efa8195c4b50d4965a8fff1b72e8efa4 -splice-dso-governance 0.1.25 93645a39701e2ca0fbf1f5744df681ee11f0caed5f1d277a467498bab1ac0de1 +splice-dso-governance 0.1.25 6666c2be6d6d396900f198b58d34794f0faf1c9a90d595a8408d13298734bcab splice-dso-governance 0.1.3 b0ae3cc03e418790305a3c15f761fe495572de5827f8d322fb8b96996b783c13 splice-dso-governance 0.1.4 dc24fd18b4d151cd1e0ff6bfb7438bafb2f50fe076d0f16f50565e60b153a0be splice-dso-governance 0.1.5 9e3ca1d22ad495dfabf3d61acae3dc1a7718f527f02092280b58cf69edfdc84c @@ -84,21 +84,21 @@ splice-dso-governance 0.1.6 4e7653cfbf7ca249de4507aca9cd3b91060e5489042a522c589d splice-dso-governance 0.1.7 d406eba1132d464605f4dae3edf8cf5ecbbb34bd8edef0e047e7e526d328718c splice-dso-governance 0.1.8 1790a114f83d5f290261fae1e7e46fba75a861a3dd603c6b4ef6b67b49053948 splice-dso-governance 0.1.9 9ee83bfd872f91e659b8a8439c5b4eaf240bcf6f19698f884d7d7993ab48c401 -splice-dso-governance-test 0.1.31 0c1cd5414d3d09bcf72ed0f9878327a88d655d068da745968e08736c275484e4 -splice-test-token-v1 1.0.0 c5c08c93b8ac50dd7e558c1faee554f0909203aa35bd6f9c19336bfa281d067b -splice-test-token-v2 1.0.0 8b238c23c5505cc544f056275f75349b7cfd0393679f8ceb942e6ae267d70d53 +splice-dso-governance-test 0.1.31 7accd9bfca0896b16fed715f3dd48ee5ce279d4301b04e4c19297796dcb7030b +splice-test-token-v1 1.0.0 ff4ee548d78f4f59379ed6b735c8e0deef141b7a022538c80845997d8c0e4b28 +splice-test-token-v2 1.0.0 c97cade6354081c9d26eff8d8c24af45be58044c1a146476a77b797fdaa04c48 splice-token-standard-test-v1 1.0.11 21e4b69eaa4fa4d20c7d0eb28295a7cadf1a8b428b6da4cf53f93f0b57c008cb splice-token-standard-test-v1 1.0.12 60254df188b3bccf23212a4f9c6fc61884b1497551670cd1991bec3df8dcae4b -splice-token-standard-test-v1 1.0.13 72b3b918c2e2bf63c7026f19600211d9ae3117e549bf1d6fe6e7c55f60ef32dc -splice-token-standard-test-v2 1.0.0 008298a4bf14e0cd0891aaf4da87e62d66e9f6a847a213fa80eeedb5e6ce4714 -splice-token-standard-utils 2.0.0 72a64a2d6a65975919c4dc72989062b6a347c3734d9792c7d97b0457e4c675be +splice-token-standard-test-v1 1.0.13 f7699e8c01d5e5a4e9fc214dd26bcce47a4650bb16d39bf59be9c3c2f794c1ef +splice-token-standard-test-v2 1.0.0 11a983e6a5e419f525f73b7920d47d93ca7c2fa01fc013c3f881de6bf0d460e7 +splice-token-standard-utils 2.0.0 8430bf0caad28ba4d56430254475c3b70e376abafd702b3d62c7c6cbb49a5191 splice-token-test-dummy-holding 0.0.1 1cd171c6c42ab46dc9cf12d80c6111369e00cea5cdf054924b4f26ce94b1ef5b splice-token-test-dummy-holding 0.0.2 4f40fb033ef3db89623642c1b494e846097fa32af138b3864a63aa15937a323d splice-token-test-dummy-holding 0.0.3 26c72abb5a4b485e58f201021de6f9e525c85863fa115536f768d6ee138ef13e splice-token-test-trading-app 1.0.0 e5c9847d5a88d3b8d65436f01765fc5ba142cc58529692e2dacdd865d9939f71 splice-token-test-trading-app 1.0.1 833ea19115848d050341dfd9f10cb7813ffa17b8d8dc40d702b2b6adbbe31697 splice-token-test-trading-app 1.0.2 ad760abaeb77145a20caa2a59b7655f9371a9e6e26ccfd63a98ceb2c7a6c8880 -splice-token-test-trading-app-v2 1.0.0 535f0008b0d4928342175d0c9e4a2a5f2a562a41debc7cb101f623a3b571df8c +splice-token-test-trading-app-v2 1.0.0 da533befddd9bde0a0f7adb49dcd94374f25773e0aa592f2b06623df8030dfc4 splice-util 0.1.0 0f319b3bcc9a418836d58c86deae25b38d3f018f1b0c117456cd9011cface7f1 splice-util 0.1.1 00bf3632ca479d56e536096ca23bbc75d15084088ab5d12e4b31d6547d1df351 splice-util 0.1.2 3eb8f9ff160b782e0bf7ef0351072cfb6f186f086e082b35d9f7a0317e163372 @@ -109,18 +109,18 @@ splice-util 0.1.6 62a6fc80fc8ca84dd5f6b33c71f53dd35a02bc01784b8cbc7cbd283eb23863 splice-util-batched-markers 1.0.0 727c5e97457d3ff841680816eb70d55834827ef756bac8551cace5b961c9c1d2 splice-util-batched-markers 1.0.1 4d91a9b044e0e996e91ee9aac3442591ffc78f16da4ff5c6f55218ba667f6192 splice-util-batched-markers 1.0.2 ec84eb67802403d91892aad61aee2e4eb34fb4e05e9158b3f677537569bcbc09 -splice-util-batched-markers-test 1.0.6 9c35e62079d456a7403d9a53bb6e7919355a637dbbde08a47e60fec76ef5783d +splice-util-batched-markers-test 1.0.6 46235d64dbc01b6cb387a7f05200038c57e3a29b113b02606dcfd59a226d38a8 splice-util-featured-app-proxies 1.0.0 48e0c4fe4ea05e3b740404ebe37004ddd741efbdcd665c1c3199a5d6d9d944d7 splice-util-featured-app-proxies 1.1.0 81dd5a9e5c02d0de03208522a895fb85eeb12fbea4aca7c4ad0ad106f3b0bfce splice-util-featured-app-proxies 1.2.0 653c48879064332d34af5008bdfd8e349493460e67e62b85e8e7e3392831c842 splice-util-featured-app-proxies 1.2.1 06bab917848ef275317c2539b75c23b94e03ceb55b4a1346936f7832084cd7a6 splice-util-featured-app-proxies 1.2.2 2889c094cf9678b2b666221934ea56ab169a31b257450845bd53217a8cdfe44f splice-util-featured-app-proxies 1.2.3 677ed3473e7d412c06520843790ff27e21aaa7c292208909579592351c8eb55d -splice-util-featured-app-proxies-test 1.0.12 ef060269522771c97e8bb07ddeaabfaf2541c76383075115b930f822df77bd61 +splice-util-featured-app-proxies-test 1.0.12 1271e989e27f1ab77eccf73d206cbcab2fb0a6c2cdd671fb31fb69195c26c93c splice-util-token-standard-wallet 1.0.0 1da198cb7968fa478cfa12aba9fdf128a63a8af6ab284ea6be238cf92a3733ac splice-util-token-standard-wallet 1.0.1 182892381e4245c39d3126082ec0b41a089edb61e63af518f61eae1f5c9e135a -splice-util-token-standard-wallet 1.1.0 38d8e0d00498dfe76e7741cfcc37ecd0ff8a16d942fa194f96971447f8b83462 -splice-util-token-standard-wallet-test 1.0.7 eae3ee1e23f493cd5cfb3bf455a1ec74b877a082b570b02cf6ecccf5d154c450 +splice-util-token-standard-wallet 1.1.0 60cf9faddfb60beb66a3b9ea91032b39d6962c1bd9168a3de806c39e226a967b +splice-util-token-standard-wallet-test 1.0.7 536c60923d9ccbd4c84a6480c4cbeede4b9342cbc38784a039bcea499f67b2a3 splice-validator-lifecycle 0.1.0 cef96fac957362f1fc097120bd13686cac7f84fbc8053afa994a1f9214d9570c splice-validator-lifecycle 0.1.1 1ddf05c96002914593c929848b786f34c753fb0be07717d1786be177a564aada splice-validator-lifecycle 0.1.2 57e2f15f9755db1f00e51c52c319294264a21ad71c6bc1e7cd70db4b164c0aaa @@ -143,7 +143,7 @@ splice-wallet 0.1.17 176c2924cd7aa12bc81ffd1a8d6cfaf46e70378f653eb5f19f2d6b9599c splice-wallet 0.1.18 94d88246f69d8a4b69333d1f993e3280deaca19b70511ea7687f01e4328a34a4 splice-wallet 0.1.19 1d8317b1e476c03ea2a85bed8435e5c182abe501db58350009187fa839ab2cca splice-wallet 0.1.2 c162e08a4ec0428bfa870b6d9040989e575c74199c3a80558c62e03196dd5146 -splice-wallet 0.1.20 092cab0a1993b39a17c55c3cd7683a920edc8cdb0ec5b7191a5b09530bd25087 +splice-wallet 0.1.20 0d87a1398b5c2973c0d10e05c6cc819edae7768ac7103f11aa93165e13879e2e splice-wallet 0.1.3 2c35bb4f5084ea66db59717d21750bfd64c43147ef5fd5166615092d592a6917 splice-wallet 0.1.4 141dad2d33b6410b8e1c35a0c4f8f76cb691e4d9a4410ce89f33f373855317e1 splice-wallet 0.1.5 614b525a50c624062d851ce7df5bdb90ddfa0d6871c486cb6e2c7b694bfbce59 @@ -162,7 +162,7 @@ splice-wallet-payments 0.1.15 f80fae7a9de9431854372a66c3ca78675f77b2f54ede65abdc splice-wallet-payments 0.1.16 45e7ac4601186747e2c4d2fd7e54a15e5752eee56d6cf767eb62141b7a10c0a8 splice-wallet-payments 0.1.17 94bba10a5b3fef448ccd28669359af3b09442a1d1bd6cdbb52c401d7d10075bc splice-wallet-payments 0.1.18 06afd4996294b3763b10fc7ed3b2b216dc3ff2196264cf7d62d0572dd3b737b8 -splice-wallet-payments 0.1.19 78042a7322f7636af2a65bb0769116d2176317c59503821189e7681e1358cf43 +splice-wallet-payments 0.1.19 b981de244e59400c97d0721622b9554d4b1b58a71a7c535de86f986c5ead0fe9 splice-wallet-payments 0.1.2 775f5eb9c0249509adda5eb3ea4ee31bb953601168c18880df6f2ff09ec4298a splice-wallet-payments 0.1.3 b953b3729c81a55e598a364be7d0c0574750df3de12a7a1b53a300f217cb5c5c splice-wallet-payments 0.1.4 12177f54873c1094ea169874ad0d7838383fd137f302d16356e93f28dfbc0fcc @@ -171,7 +171,7 @@ splice-wallet-payments 0.1.6 6124379528eeb6fa17ecdab15577c29abb33d0c0d34dc5f2680 splice-wallet-payments 0.1.7 4e3e0d9cdadf80f4bf8f3cd3660d5287c084c9a29f23c901aabce597d72fd467 splice-wallet-payments 0.1.8 e48ea337ee3335c8bb3206a2501ce947ac1a7bdb1825cee8f28bad64f5a7bc4b splice-wallet-payments 0.1.9 7f4e081ad96f2ccded0c053b0cf5ddddae1139dfc3bb89cefcf77ea70f2cecb7 -splice-wallet-test 0.1.24 fa26c418c2e1f2299f613da6f34892a5fc22be72937c54f96683084c123ce712 +splice-wallet-test 0.1.24 b691728c6656812c4a0859aec5ae50ec770bf36ae6c20e7796e8c5a5c0b90a9a splitwell 0.1.0 075c76de553ab88383a7c69de134afa82aacfdf8ea8fcfe8852c4b199c3b2669 splitwell 0.1.1 ccb1a0215053062202052e1a052f9214da3fdae5253a6d43e2e155ff4f57fe75 splitwell 0.1.10 d42676a366f7ca7a2409974dd3054aa4d83ab29baa3b2086ad021407b0a1a295 @@ -185,7 +185,7 @@ splitwell 0.1.17 a631654e66ef31017bf3c9cb4ab2429157d5e5f948f1b6b15a38f0ec7c0cd36 splitwell 0.1.18 4694a5545800c7b98cdd7e7349c98f037931bb91574a76715d52da9c647c4081 splitwell 0.1.19 b526511ebe2db308b63969ee3c04aa9a48be53e04130709eb29ba6aa313e3cc4 splitwell 0.1.2 778edd2c228c6b68198d4d033885b2d0dae7daaee55d7df3edd9dfdf1f10fbd0 -splitwell 0.1.20 b4f9a49c7d91f4a352212ddcb4d5f5024e12143ee695cb55e01b2d6ed411c530 +splitwell 0.1.20 00064f83b946fa3deea863c0f5cb92d62fefa2b507b3232a747463a2b2e2060b splitwell 0.1.3 7cde068cde689584f86a2499689d5cb165264d96496721e24ac6fb909f770a58 splitwell 0.1.4 85557b86cd4f330f093915db1ea26eac5092de6b5ddae0690146f6059c89419b splitwell 0.1.5 a68e78774a7be655f5744c8ae0ac8b46d55ef6d1e7661bc27b9296154d56ac74 @@ -193,4 +193,4 @@ splitwell 0.1.6 872da0dd7986fd768930f85d6a7310a94a0ef924e7fbb7bb7a4e149f2b5feb74 splitwell 0.1.7 841d1c9c86b5c8f3a39059459ecd8febedf7703e18f117300bb0ebf4423db096 splitwell 0.1.8 63b8153a08ceb4bf40d807acc5712372c3eac548c266be4d5e92470b4f655515 splitwell 0.1.9 b6267905698d2798b9ef171e27d49fb88e052ec0ec0e0675a3a1b275c7d037d4 -splitwell-test 0.1.24 f69a8c4a60a3cc54b41c2394dcbdf0d2285c81f2d6256449ef07c2799c79ad31 \ No newline at end of file +splitwell-test 0.1.24 e5f55d51ec088ef443cf37f203e457d4acbb60cd63b90ff31015033aa30f988c \ No newline at end of file diff --git a/daml/dars/splice-amulet-0.1.19.dar b/daml/dars/splice-amulet-0.1.19.dar index d2f6734573..ed12a838c3 100644 Binary files a/daml/dars/splice-amulet-0.1.19.dar and b/daml/dars/splice-amulet-0.1.19.dar differ diff --git a/daml/dars/splice-amulet-name-service-0.1.20.dar b/daml/dars/splice-amulet-name-service-0.1.20.dar index 4257f283a3..7b4384e2ff 100644 Binary files a/daml/dars/splice-amulet-name-service-0.1.20.dar and b/daml/dars/splice-amulet-name-service-0.1.20.dar differ diff --git a/daml/dars/splice-api-token-allocation-instruction-v2-1.0.0.dar b/daml/dars/splice-api-token-allocation-instruction-v2-1.0.0.dar index 1b821ff769..18c59db48c 100644 Binary files a/daml/dars/splice-api-token-allocation-instruction-v2-1.0.0.dar and b/daml/dars/splice-api-token-allocation-instruction-v2-1.0.0.dar differ diff --git a/daml/dars/splice-api-token-allocation-request-v2-1.0.0.dar b/daml/dars/splice-api-token-allocation-request-v2-1.0.0.dar index 94bd94e5e2..c4479c7dcd 100644 Binary files a/daml/dars/splice-api-token-allocation-request-v2-1.0.0.dar and b/daml/dars/splice-api-token-allocation-request-v2-1.0.0.dar differ diff --git a/daml/dars/splice-api-token-allocation-v2-1.0.0.dar b/daml/dars/splice-api-token-allocation-v2-1.0.0.dar index 575da8dc47..f99c7f4880 100644 Binary files a/daml/dars/splice-api-token-allocation-v2-1.0.0.dar and b/daml/dars/splice-api-token-allocation-v2-1.0.0.dar differ diff --git a/daml/dars/splice-api-token-transfer-events-v2-1.0.0.dar b/daml/dars/splice-api-token-transfer-events-v2-1.0.0.dar index a618b2efa6..7b06639346 100644 Binary files a/daml/dars/splice-api-token-transfer-events-v2-1.0.0.dar and b/daml/dars/splice-api-token-transfer-events-v2-1.0.0.dar differ diff --git a/daml/dars/splice-dso-governance-0.1.25.dar b/daml/dars/splice-dso-governance-0.1.25.dar index a608cf300c..1175ef8f62 100644 Binary files a/daml/dars/splice-dso-governance-0.1.25.dar and b/daml/dars/splice-dso-governance-0.1.25.dar differ diff --git a/daml/dars/splice-test-token-v1-1.0.0.dar b/daml/dars/splice-test-token-v1-1.0.0.dar index d6fa88da9d..b3fce86ce6 100644 Binary files a/daml/dars/splice-test-token-v1-1.0.0.dar and b/daml/dars/splice-test-token-v1-1.0.0.dar differ diff --git a/daml/dars/splice-test-token-v2-1.0.0.dar b/daml/dars/splice-test-token-v2-1.0.0.dar index 17da047024..13e158649e 100644 Binary files a/daml/dars/splice-test-token-v2-1.0.0.dar and b/daml/dars/splice-test-token-v2-1.0.0.dar differ diff --git a/daml/dars/splice-token-standard-test-v1-1.0.13.dar b/daml/dars/splice-token-standard-test-v1-1.0.13.dar index 54256b2eaa..b1e9c78b5e 100644 Binary files a/daml/dars/splice-token-standard-test-v1-1.0.13.dar and b/daml/dars/splice-token-standard-test-v1-1.0.13.dar differ diff --git a/daml/dars/splice-token-standard-test-v2-1.0.0.dar b/daml/dars/splice-token-standard-test-v2-1.0.0.dar index bd85ca2cc1..b4e17826ab 100644 Binary files a/daml/dars/splice-token-standard-test-v2-1.0.0.dar and b/daml/dars/splice-token-standard-test-v2-1.0.0.dar differ diff --git a/daml/dars/splice-token-standard-utils-2.0.0.dar b/daml/dars/splice-token-standard-utils-2.0.0.dar index 3dca94c2a8..25cad895c1 100644 Binary files a/daml/dars/splice-token-standard-utils-2.0.0.dar and b/daml/dars/splice-token-standard-utils-2.0.0.dar differ diff --git a/daml/dars/splice-token-test-trading-app-v2-1.0.0.dar b/daml/dars/splice-token-test-trading-app-v2-1.0.0.dar index cd699c5c24..ed081ed7aa 100644 Binary files a/daml/dars/splice-token-test-trading-app-v2-1.0.0.dar and b/daml/dars/splice-token-test-trading-app-v2-1.0.0.dar differ diff --git a/daml/dars/splice-util-token-standard-wallet-1.1.0.dar b/daml/dars/splice-util-token-standard-wallet-1.1.0.dar index 8e658efd04..5042c048f2 100644 Binary files a/daml/dars/splice-util-token-standard-wallet-1.1.0.dar and b/daml/dars/splice-util-token-standard-wallet-1.1.0.dar differ diff --git a/daml/dars/splice-wallet-0.1.20.dar b/daml/dars/splice-wallet-0.1.20.dar index 154017173a..8641c9525b 100644 Binary files a/daml/dars/splice-wallet-0.1.20.dar and b/daml/dars/splice-wallet-0.1.20.dar differ diff --git a/daml/dars/splice-wallet-payments-0.1.19.dar b/daml/dars/splice-wallet-payments-0.1.19.dar index 1ff160e97a..06f2f4c487 100644 Binary files a/daml/dars/splice-wallet-payments-0.1.19.dar and b/daml/dars/splice-wallet-payments-0.1.19.dar differ diff --git a/daml/dars/splitwell-0.1.20.dar b/daml/dars/splitwell-0.1.20.dar index e2f94ce3fc..bbba5e7c43 100644 Binary files a/daml/dars/splitwell-0.1.20.dar and b/daml/dars/splitwell-0.1.20.dar differ diff --git a/daml/splice-amulet-name-service-test/daml/Splice/Scripts/TestAns.daml b/daml/splice-amulet-name-service-test/daml/Splice/Scripts/TestAns.daml index 79b8cd3eea..565104e3a3 100644 --- a/daml/splice-amulet-name-service-test/daml/Splice/Scripts/TestAns.daml +++ b/daml/splice-amulet-name-service-test/daml/Splice/Scripts/TestAns.daml @@ -73,8 +73,8 @@ testWithSubscription = script do [unlockChange, paymentChange] <- pure changes WalletClientV2.expectTxHistoryReasons [unlockChange] [(TSU.basicAccount alice.primaryParty, [], "holders released lock")] WalletClientV2.expectTxHistoryReasons [paymentChange] [(TSU.basicAccount alice.primaryParty, [paymentDesc], "")] - [paymentLeg] <- pure paymentChange.transferLegs - paymentLeg.receiver === TSU.burnAccount app.dso -- ANS entry payment is marked as burn + [paymentLeg] <- pure paymentChange.transferLegSides + paymentLeg.otherside === TSU.burnAccount app.dso -- ANS entry payment is marked as burn checkPaymentCollectionHistory changes -- the amulet received by sv is brunt diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml b/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml index f34f39626b..f35e6603d3 100644 --- a/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml +++ b/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml @@ -678,7 +678,7 @@ testInputDevelopmentFundCoupon = do [ ("amulet.splice.lfdecentralizedtrust.org/development-fund", show totalAmount) , ("splice.lfdecentralizedtrust.org/reason", "mint rewards")] [change] <- pure changes - [leg] <- pure change.transferLegs + [leg] <- pure change.transferLegSides leg.meta === expectedMetadata pure () diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/TestTransferPreapproval.daml b/daml/splice-amulet-test/daml/Splice/Scripts/TestTransferPreapproval.daml index 771ae05bd0..4170c9b2bb 100644 --- a/daml/splice-amulet-test/daml/Splice/Scripts/TestTransferPreapproval.daml +++ b/daml/splice-amulet-test/daml/Splice/Scripts/TestTransferPreapproval.daml @@ -75,7 +75,7 @@ testTransferPreapprovalSend = script do error ("Expected exactly 2 holdings changes emitted, got: " <> show changes) let expectedMeta = Metadata $ TextMap.fromList [("acme.com/correlation-id", ""), ("splice.lfdecentralizedtrust.org/reason","test-description V2 choice")] - forA_ changes $ \change -> map (.meta) change.transferLegs === [expectedMeta] + forA_ changes $ \change -> map (.meta) change.transferLegSides === [expectedMeta] TextMap.lookup reasonMetaKey result.meta.values === Some "test-description V2 choice" diff --git a/daml/splice-amulet/daml/Splice/Amulet.daml b/daml/splice-amulet/daml/Splice/Amulet.daml index 331d689dce..7ce6d3c163 100644 --- a/daml/splice-amulet/daml/Splice/Amulet.daml +++ b/daml/splice-amulet/daml/Splice/Amulet.daml @@ -587,7 +587,7 @@ logAmuletUnlock dso reason owner lockedAmuletCid unlockedAmuletCid = do admin = dso account = basicAccount owner inputHoldingCids = [toInterfaceContractId lockedAmuletCid] - transferLegs = [] + transferLegSides = [] outputHoldingCids = [toInterfaceContractId unlockedAmuletCid] observers = [owner] extraArgs = ExtraArgs with diff --git a/daml/splice-amulet/daml/Splice/AmuletAllocation.daml b/daml/splice-amulet/daml/Splice/AmuletAllocation.daml index a75ec52496..1f1a6a438c 100644 --- a/daml/splice-amulet/daml/Splice/AmuletAllocation.daml +++ b/daml/splice-amulet/daml/Splice/AmuletAllocation.daml @@ -107,7 +107,8 @@ template AmuletAllocation -- NOTE: we report this as a burn in the V1 history metadata, as representing the full -- set of transfer legs is very difficult to do using the V1 metadata. pure V2.AllocationResult with - output = V2.AllocationResult_Settled + output = V2.AllocationResult_Settled with + nextIterationAllocationCid = None -- no iterative settlement for V1 allocations authorizerHoldingCids = mempty meta = v1HistoryMeta diff --git a/daml/splice-amulet/daml/Splice/AmuletAllocationV2.daml b/daml/splice-amulet/daml/Splice/AmuletAllocationV2.daml index 29e07d79ce..57d89ad248 100644 --- a/daml/splice-amulet/daml/Splice/AmuletAllocationV2.daml +++ b/daml/splice-amulet/daml/Splice/AmuletAllocationV2.daml @@ -2,18 +2,19 @@ -- SPDX-License-Identifier: Apache-2.0 -- | V2 amulet allocation, which can and should be settled using `V2.SettlementFactory_SettleBatch`. --- --- TODO(#5024): attempt to remove this whole module in favor of implementing full V2 allocation support in AmuletAllocation module Splice.AmuletAllocationV2 ( AmuletAllocationV2(..), netAmuletCreditAmount, + validateAmuletNextIterationFunding, + computeAllocationExpiry, ) where +import DA.Action import DA.Assert (assertWithinDeadline) import DA.Foldable (forA_) -import DA.Optional (isNone) +import DA.Optional import DA.TextMap qualified as TextMap -import DA.Map qualified as Map +import DA.Time import Splice.Api.Token.MetadataV1 import Splice.Api.Token.HoldingV2 qualified as V2 @@ -22,13 +23,16 @@ import Splice.TokenStandard.Utils hiding (require) import Splice.Amulet import Splice.AmuletRules +import Splice.AmuletAllocation qualified as AmuletAllocationV1 import Splice.Amulet.TwoStepTransfer import Splice.Amulet.TokenApiUtils +import Splice.Expiry (TimeLock(..)) import Splice.Fees (expiringAmount) import Splice.Types import Splice.Util + -- | V2 amulet allocation created using the `V2.AllocationFactory_Allocate` -- choice and expected to be settled using `V2.SettlementFactory_SettleBatch`. template AmuletAllocationV2 @@ -39,21 +43,23 @@ template AmuletAllocationV2 allocation : V2.AllocationSpecification dso : Party expiresAt : Time + numIterations : Int + createdAt : Time where signatory dso, allocation.authorizer.owner observer allocation.settlement.executors ensure isBasicAccount allocation.authorizer && - isValidAllocationSpecificationV2 - (\instrumentId -> instrumentId == amuletInstrumentIdV2 dso) - allocation + isValidAllocationSpecificationV2 (== amuletInstrumentIdName) allocation -- TODO(#4525): add expiry choice for the DSO party and hook it up to AmuletRules, so that AmuletAllocationV2 is also auto-expired interface instance V2.Allocation for AmuletAllocationV2 where view = V2.AllocationView with originalAllocationCid = None + createdAt + numIterations expiresAt = Some expiresAt allocation holdingCids = case lockedAmulet of @@ -72,6 +78,7 @@ template AmuletAllocationV2 allocation_withdrawImpl self arg@(V2.Allocation_Withdraw{..}) = do archiveAndCheckActors self arg.actors [[allocation.authorizer.owner]] + ensureWithdrawIsAllowed allocation authorizerHoldingCids <- unlockAmuletAllocationV2 this extraArgs pure V2.AllocationResult with output = V2.AllocationResult_Withdrawn @@ -86,65 +93,97 @@ template AmuletAllocationV2 authorizerHoldingCids meta = emptyMetadata - allocation_settleImpl self arg@(V2.Allocation_Settle{..}) = do + allocation_settleImpl self arg = do -- check actors and deadline archiveAndCheckActors self arg.actors [dso :: allocation.settlement.executors] forA_ allocation.settlement.settlementDeadline $ \deadline -> assertWithinDeadline "allocation.settlement.settlementDeadline" deadline - -- get event log to report transfer events for V2 transaction history parsing - (configStateCid, _) <- getExternalPartyConfigStateFromChoiceContext dso extraArgs.context - - -- compute total funds to move + -- validate the Allocation_Settle choice arguments + let actualTransferLegSides = allocation.transferLegSides ++ arg.extraTransferLegSides + -- note: we allow allocations with no transfer legs, as they can be used to bump the expiry, + -- and the traffic cost of that bump is what protects the SV node's storage space + unless (isSome allocation.nextIterationFunding) $ do + require "no extra transfer legs allowed when iterated settlement is disabled" (null arg.extraTransferLegSides) + require "no next iteration funding allowed when iterated settlement is disabled" (isNone arg.nextIterationFunding) + validateAmuletTransferLegs actualTransferLegSides + outputFundingAmount <- validateAmuletNextIterationFunding arg.nextIterationFunding + + -- archive the locked tokens let authorizer = allocation.authorizer.owner - let netAmount = netAmuletCreditAmount dso (basicAccount authorizer) allocation.transferLegs - if netAmount >= 0.0 then do - require "No locked amulet expected for credit amount >= 0" (isNone lockedAmulet) - if netAmount > 0.0 - then do - -- Net credit of Amulet ==> create the holding - (_cid, configState) <- getExternalPartyConfigStateFromChoiceContext dso extraArgs.context - let configAmulet = transferConfigAmuletFromExternalPartyConfigState configState - - newHoldingCid <- toInterfaceContractId <$> create Amulet with - dso - owner = authorizer - amount = expiringAmount configAmulet.holdingFee netAmount configState.holdingFeesOpenRoundNumber - - logAllocationSettlement (toInterfaceContractId configStateCid) [] allocation [newHoldingCid] - - let reason = "Settle net positive allocation for settlement " <> allocation.settlement.settlementRef.id - pure V2.AllocationResult with - output = V2.AllocationResult_Settled - authorizerHoldingCids = TextMap.fromList [(amuletInstrumentIdName, [newHoldingCid])] - meta = simpleHoldingTxMeta TxKind_Mint (Some reason) None - -- Note: for V1 tx history parsing, it is difficult to do better - -- than reporting this as a mint, as explained below for TxKind_Burn. - else do - -- No holding change ==> no tx history metadata - pure V2.AllocationResult with - output = V2.AllocationResult_Settled - authorizerHoldingCids = mempty - meta = emptyMetadata - else do - -- Net debit of Amulet ==> archive the locked amulet + (inputFundingAmount, inputTokenCids) <- case lockedAmulet of - None -> fail "Expected locked amulet for negative net amount" + None -> pure (0.0, []) Some lockedAmuletCid -> do lockedAmulet <- fetchAndArchive (ForOwner with dso = dso; owner = authorizer) lockedAmuletCid - requireMatchExpected ("lockedAmulet.amount", lockedAmulet.amulet.amount.initialAmount) (negate netAmount) - - logAllocationSettlement (toInterfaceContractId configStateCid) [toInterfaceContractId lockedAmuletCid] allocation [] + pure (lockedAmulet.amulet.amount.initialAmount, [toInterfaceContractId lockedAmuletCid]) - let reason = "Settle net negative allocation for settlement " <> show allocation.settlement.settlementRef.id - pure V2.AllocationResult with - output = V2.AllocationResult_Settled - authorizerHoldingCids = mempty - meta = simpleHoldingTxMeta TxKind_Burn (Some reason) None - -- Note: for V1 tx history parsing, we can't really do better reporting this as a burn, as we don't - -- have the option of reporting all the actual transfers independently, as we do for V2 tx history parsing. - -- We accept this short-coming, as wallets can easily start parsing V2 events independently fo doing the work - -- to fully support all V2 flows. + -- compute total funds to move + let netTransferAmount = netAmuletCreditAmount (basicAccount authorizer) actualTransferLegSides + let inputAmount = inputFundingAmount + netTransferAmount + let payoutAmount = inputAmount - outputFundingAmount + require' ("inputAmount", inputAmount) isGreaterOrEqualR ("minimal outputAmount", outputFundingAmount) + + -- read reference data + (configStateCid, configState) <- getExternalPartyConfigStateFromChoiceContext dso arg.extraArgs.context + let configAmulet = transferConfigAmuletFromExternalPartyConfigState configState + + -- create the payout + payoutHoldingCids <- + if payoutAmount <= 0.0 then pure [] else do + amuletCid <- create Amulet with + dso + owner = authorizer + amount = expiringAmount configAmulet.holdingFee payoutAmount configState.holdingFeesOpenRoundNumber + pure [toInterfaceContractId amuletCid] + + -- create the locked amulet for the next iteration, if needed + let settlement = allocation.settlement + newExpiresAt <- bumpAllocationExpiry expiresAt settlement + lockedAmulet <- + if outputFundingAmount <= 0.0 then pure None else do + lockedAmuletCid <- create LockedAmulet with + lock = TimeLock with + holders = [authorizer, dso] + expiresAt = newExpiresAt + optContext = Some $ AmuletAllocationV1.mkAllocationLockContext (downcast settlement.settlementRef) + amulet = Amulet with + dso + owner = authorizer + amount = expiringAmount configAmulet.holdingFee outputFundingAmount configState.holdingFeesOpenRoundNumber + pure (Some lockedAmuletCid) + + -- create the allocation for the next iteration, if needed + nextIterationAllocationCid <- + if isNone arg.nextIterationFunding then pure None else do + nextAllocationCid <- create AmuletAllocationV2 with + lockedAmulet + allocation = this.allocation with + transferLegSides = [] + nextIterationFunding = arg.nextIterationFunding + dso + expiresAt = newExpiresAt + numIterations = numIterations + 1 + createdAt + pure (Some (toInterfaceContractId nextAllocationCid)) + + -- log the tx history v2 event + let outputHoldingCids = optional [] (pure . toInterfaceContractId) lockedAmulet ++ payoutHoldingCids + logAllocationSettlement (toInterfaceContractId configStateCid) + inputTokenCids + (allocation with transferLegSides = actualTransferLegSides) + outputHoldingCids + + -- return the result + let v1Reason = "Settle allocation for settlement " <> allocation.settlement.settlementRef.id + pure V2.AllocationResult with + authorizerHoldingCids = TextMap.fromList [(amuletInstrumentIdName, outputHoldingCids) | not (null outputHoldingCids)] + output = V2.AllocationResult_Settled with + nextIterationAllocationCid + meta = simpleHoldingTxMeta TxKind_Transfer (Some v1Reason) None + -- Note: we approximate the metadata for V1 history parsing by declaring it as a transfer, even though there + -- might be multiple transfers happening at once. It's the best we can do given the limited expressivity + -- of V1 tx history metadata. unlockAmuletAllocationV2 : AmuletAllocationV2 -> ExtraArgs -> Update (TextMap.TextMap [ContractId V2.Holding]) @@ -156,15 +195,49 @@ unlockAmuletAllocationV2 amuletAllocation extraArgs = do holdingCids <- abortTwoStepTransfer lockExpiresAt la extraArgs pure $ TextMap.fromList [ (amuletInstrumentIdName, map upcast holdingCids) ] - -netAmuletCreditAmount : Party -> V2.Account -> [V2.TransferLeg] -> Decimal -netAmuletCreditAmount dso authorizer transferLegs = - case Map.lookup amuletId creditAmounts of - None -> error $ "netAmuletCreditAmount: no legs for amulet instrument: " <> show (amuletId, transferLegs) - Some creditAmount -> creditAmount +netAmuletCreditAmount : V2.Account -> [V2.TransferLegSide] -> Decimal +netAmuletCreditAmount authorizer transferLegs = + fromOptional 0.0 $ TextMap.lookup amuletInstrumentIdName creditAmounts where creditAmounts = netAllocationCreditAmounts authorizer transferLegs - amuletId = amuletInstrumentIdV2 dso + +validateAmuletNextIterationFunding : Optional (TextMap.TextMap Decimal) -> Update Decimal +validateAmuletNextIterationFunding None = pure 0.0 +validateAmuletNextIterationFunding (Some nextIterationFunding) = do + amounts <- forA (TextMap.toList nextIterationFunding) $ \(instrumentId, amount) -> do + requireMatchExpected ("nextIterationFunding.instrumentId", instrumentId) amuletInstrumentIdName + require' ("nextIterationFunding.amount", amount) isGreaterOrEqualR ("zero", 0.0) + pure amount + pure (sum amounts) + +validateAmuletTransferLegs : [V2.TransferLegSide] -> Update () +validateAmuletTransferLegs transferLegs = do + requireUnique "transferLegSides" (map (\leg -> (leg.transferLegId, leg.side)) transferLegs) + forA_ transferLegs $ \leg -> do + requireMatchExpected ("transferLeg.instrumentId", leg.instrumentId) amuletInstrumentIdName + require' ("transferLeg.amount", leg.amount) isGreaterOrEqualR ("zero", 0.0) + +-- | Minimum time-to-live for an amulet allocation. The maximum is then twice that much. +-- TODO(#4665): make max duration configurable; and also implement for transfer instructions +minAmuletAllocationTTL : RelTime +minAmuletAllocationTTL = days 45 + +maxAmuletAllocationTTL : RelTime +maxAmuletAllocationTTL = minAmuletAllocationTTL + minAmuletAllocationTTL + +computeAllocationExpiry : Time -> V2.SettlementInfo -> Time +computeAllocationExpiry now settlement = + optional identity min settlement.settlementDeadline $ + now `addRelTime` maxAmuletAllocationTTL + +-- | Bump the allocation expiry such that the allocation has an expiry +-- time between `minAmuletAllocationTTL` and `maxAmuletAllocationTTL` from now. +bumpAllocationExpiry : Time -> V2.SettlementInfo -> Update Time +bumpAllocationExpiry currentExpiresAt settlement = do + dontBump <- isLedgerTimeLE (currentExpiresAt `addRelTime` negate minAmuletAllocationTTL) + pure $ if dontBump then currentExpiresAt else + optional identity min settlement.settlementDeadline $ + addRelTime currentExpiresAt minAmuletAllocationTTL -- instances diff --git a/daml/splice-amulet/daml/Splice/AmuletRules.daml b/daml/splice-amulet/daml/Splice/AmuletRules.daml index 90fb25f0d1..9ae3aafb27 100644 --- a/daml/splice-amulet/daml/Splice/AmuletRules.daml +++ b/daml/splice-amulet/daml/Splice/AmuletRules.daml @@ -29,7 +29,7 @@ import Splice.Api.Token.HoldingV1 qualified as Api.Token.HoldingV1 import Splice.Api.Token.TransferInstructionV1(TransferInstruction_Update(..), TransferInstructionResult, TransferInstruction) import Splice.TokenStandard.Utils - ( basicAccount, upcast, eventLog_holdingsChangeDefaultImpl, reasonToMeta, mintAccount, burnAccount ) + ( basicAccount, eventLog_holdingsChangeDefaultImpl, reasonToMeta, mintAccount, burnAccount ) import Splice.Amulet import Splice.Amulet.TokenApiUtils @@ -1023,12 +1023,12 @@ executeTransfer' config csum dso t = do fromOptional 0.0 summary.inputDevelopmentFundAmount let optMintLeg = do guard (mintAmount > 0.0) - pure $ TransferEventsV2.TransferLeg with + pure $ TransferEventsV2.TransferLegSide with transferLegId = "mint" - sender = mintAccount dso - receiver = basicAccount t.sender + side = TransferEventsV2.ReceiverSide + otherside = mintAccount dso amount = mintAmount - instrumentId = amuletInstrumentIdV2 dso + instrumentId = amuletInstrumentIdName meta = Metadata with values = TextMap.insert reasonMetaKey "mint rewards" $ @@ -1037,7 +1037,7 @@ executeTransfer' config csum dso t = do admin = dso account = basicAccount t.sender inputHoldingCids = [ toInterfaceContractId cid | InputAmulet cid <- t.inputs ] - transferLegs = optionalToList optMintLeg + transferLegSides = optionalToList optMintLeg outputHoldingCids = [ toInterfaceContractId cid | Some cid <- pure senderChangeAmulet ] observers = [t.sender] extraArgs = ExtraArgs with @@ -1476,19 +1476,19 @@ createTransferOutputs currentOpenRound transferConfigAmulet dso sender summary p let receiverAccount | out.owner == dso = burnAccount dso -- transfers to the DSO party are always burns | otherwise = basicAccount out.owner - let leg = TransferEventsV2.TransferLeg with + let leg side otherside = TransferEventsV2.TransferLegSide with transferLegId = "leg" <> show i - sender = senderAccount - receiver = receiverAccount + side + otherside amount = out.amount - instrumentId = upcast (amuletInstrumentId dso) + instrumentId = amuletInstrumentIdName meta = fromOptional emptyMetadata out.meta let emptyExtraArgs = ExtraArgs with context = emptyChoiceContext; meta = emptyMetadata let senderEvent = TransferEventsV2.EventLog_HoldingsChange with admin = dso account = senderAccount inputHoldingCids = [] -- sender input holdings are recorded separately - transferLegs = [leg] + transferLegSides = [leg TransferEventsV2.SenderSide receiverAccount] outputHoldingCids = [] -- sender change is recorded separately observers = [sender] extraArgs = emptyExtraArgs @@ -1496,12 +1496,11 @@ createTransferOutputs currentOpenRound transferConfigAmulet dso sender summary p admin = dso account = receiverAccount inputHoldingCids = [] -- no receiver input holdings - transferLegs = [leg] + transferLegSides = [leg TransferEventsV2.ReceiverSide senderAccount] outputHoldingCids = [outputHoldingCid] -- the created output holding observers = [out.owner] extraArgs = emptyExtraArgs let events - | out.owner == sender = [receiverEvent] -- self-transfers are reported only once | out.owner == dso = [senderEvent] -- the DSO party does not need holding change events | otherwise = [senderEvent, receiverEvent] pure (createdAmulet, events) @@ -2137,7 +2136,7 @@ mergeHoldingsChangeEventsByAccount events = mergeTwoHoldingChangeEvents e1 e2 = e1 with inputHoldingCids = e1.inputHoldingCids ++ e2.inputHoldingCids - transferLegs = e1.transferLegs ++ e2.transferLegs + transferLegSides = e1.transferLegSides ++ e2.transferLegSides outputHoldingCids = e1.outputHoldingCids ++ e2.outputHoldingCids observers = dedupSort (e1.observers ++ e2.observers) extraArgs = ExtraArgs with diff --git a/daml/splice-amulet/daml/Splice/ExternalPartyAmuletRules.daml b/daml/splice-amulet/daml/Splice/ExternalPartyAmuletRules.daml index e1df698086..d822634687 100644 --- a/daml/splice-amulet/daml/Splice/ExternalPartyAmuletRules.daml +++ b/daml/splice-amulet/daml/Splice/ExternalPartyAmuletRules.daml @@ -4,11 +4,11 @@ {-# LANGUAGE MultiWayIf #-} module Splice.ExternalPartyAmuletRules where +import DA.Action import DA.Assert import DA.Foldable (forA_) import DA.Optional import qualified DA.TextMap as TextMap -import DA.Time import Splice.Api.Token.MetadataV1 import Splice.Api.Token.TransferInstructionV1 qualified as Api.Token.TransferInstructionV1 @@ -339,7 +339,7 @@ amulet_transferFactory_transferImpl this _self arg = do -- TODO(#4665): limit time between requestedAt and executeBefore, i.e., max lock duration (and add to release notes) - -- execute the right kind of transfer + -- execute the right direction of transfer case optPreapprovalCid of None | transfer.receiver == transfer.sender -> do @@ -414,15 +414,18 @@ amulet_allocationFactoryV2_allocateImpl -> V2.AllocationFactory_Allocate -> Update V2.AllocationInstructionResult amulet_allocationFactoryV2_allocateImpl externalAmuletRules _self arg = do - let creditAmount = netAmuletCreditAmount externalAmuletRules.dso arg.allocation.authorizer arg.allocation.transferLegs - let debitAmount = negate creditAmount + let netTransferAmount = netAmuletCreditAmount arg.allocation.authorizer arg.allocation.transferLegSides + outputFundingAmount <- validateAmuletNextIterationFunding arg.allocation.nextIterationFunding + let requiredFundingAmount = outputFundingAmount - netTransferAmount (lockedAmulet, senderChangeCids, expiresAt, meta) <- - amulet_allocationFactoryV2_allocateImplCore externalAmuletRules arg debitAmount + amulet_allocationFactoryV2_allocateImplCore externalAmuletRules arg requiredFundingAmount allocationCid <- toInterfaceContractId <$> create AmuletAllocationV2 with allocation = arg.allocation lockedAmulet dso = externalAmuletRules.dso expiresAt + createdAt = arg.requestedAt + numIterations = 0 pure V2.AllocationInstructionResult with authorizerChangeCids = TextMap.fromList [(amuletInstrumentIdName, map upcast senderChangeCids)] output = V2.AllocationInstructionResult_Completed with allocationCid @@ -448,35 +451,31 @@ amulet_allocationFactoryV2_allocateImplCore externalAmuletRules arg fundingAmoun -- == validate each field of the requested allocation let settlement = allocation.settlement - let transferLegs = allocation.transferLegs + let transferLegSides = allocation.transferLegSides -- settlement.executors: no check -- settlement.settlementRef: no check - -- settlement.requestedAt: - assertDeadlineExceeded "Allocation.settlement.requestedAt" settlement.requestedAt -- settlement.settlementDeadline: whenSome settlement.settlementDeadline \d -> do - assertWithinDeadline "Allocation.settlement.settlementDeadline" d + assertWithinDeadline "Allocation.settlement.settlementDeadline" d - let - -- TODO(#4665): make max duration configurable; and also implement for transfer instructions - latestExpiresAt = requestedAt `addRelTime` days 90 - expiresAt = optional latestExpiresAt (min latestExpiresAt) allocation.settlement.settlementDeadline - require "Allocation.settlement.settleAt <= Allocation.expiresAt" - (settlement.settleAt <= expiresAt) + + -- note: nextIterationFunding is validated in `amulet_allocationFactoryV2_allocateImpl`, + -- as it is only supported for V2 factories -- transferLegs: - requireUnique "allocation.transferLegs" transferLegs - forA_ transferLegs $ \transferLeg -> do + requireUnique "allocation.transferLegSides (id + side)" (map (\leg -> (leg.transferLegId, leg.side)) transferLegSides) + unless (isSome allocation.nextIterationFunding) $ + require' ("transferLegSides", transferLegSides) (notR isEqualR) ("empty list", []) + forA_ transferLegSides $ \transferLeg -> do -- transferLegId: no check - -- transferLeg.sender - ensureBasicAccount "transferLeg.sender" transferLeg.sender - -- transferLeg.receiver - ensureBasicAccount "transferLeg.receiver" transferLeg.receiver + -- transferLeg.side: no check + -- transferLeg.otherside: + ensureBasicAccount "transferLeg.otherside" transferLeg.otherside -- transferLeg.amount require "Transfer amount must be positive" (transferLeg.amount > 0.0) -- transferLeg.instrumentId - require "Instrument-id must match the factory" (transferLeg.instrumentId == amuletInstrumentIdV2 dso) + require "Instrument-id must match the factory" (transferLeg.instrumentId == amuletInstrumentIdName) -- transferLeg.meta: check no redundant account information ensureNoAccountMeta "transferLeg" "sender" transferLeg.meta ensureNoAccountMeta "transferLeg" "receiver" transferLeg.meta @@ -485,6 +484,7 @@ amulet_allocationFactoryV2_allocateImplCore externalAmuletRules arg fundingAmoun assertDeadlineExceeded "requestedAt" requestedAt -- create locked amulet if required + let expiresAt = computeAllocationExpiry requestedAt settlement if fundingAmount <= 0.0 then do -- return input cids as change, so they can be reused on chaining diff --git a/daml/splice-amulet/daml/Splice/Types.daml b/daml/splice-amulet/daml/Splice/Types.daml index 341badc500..09f107f881 100644 --- a/daml/splice-amulet/daml/Splice/Types.daml +++ b/daml/splice-amulet/daml/Splice/Types.daml @@ -70,16 +70,8 @@ instance HasCheckedFetch V2.TransferInstructionView ForDso where instance HasCheckedFetch V2.AllocationView ForDso where contractGroupId V2.AllocationView {..} = ForDso with - dso = - let legs = allocation.transferLegs - in case legs of - [] -> error "HasCheckedFetch: V2.AllocationView with empty transfer legs" - (leg :: _) -> leg.instrumentId.admin + dso = allocation.admin instance HasCheckedFetch V2.AllocationInstructionView ForDso where contractGroupId V2.AllocationInstructionView {..} = ForDso with - dso = - let legs = allocation.transferLegs - in case legs of - [] -> error "HasCheckedFetch: V2.AllocationView with empty transfer legs" - (leg :: _) -> leg.instrumentId.admin + dso = allocation.admin diff --git a/daml/splice-util-token-standard-wallet/daml/Splice/Util/Token/Wallet/BatchingUtilityV2.daml b/daml/splice-util-token-standard-wallet/daml/Splice/Util/Token/Wallet/BatchingUtilityV2.daml index daef81f82f..54c9c456c8 100644 --- a/daml/splice-util-token-standard-wallet/daml/Splice/Util/Token/Wallet/BatchingUtilityV2.daml +++ b/daml/splice-util-token-standard-wallet/daml/Splice/Util/Token/Wallet/BatchingUtilityV2.daml @@ -233,10 +233,10 @@ executeTokenStandardAction actor (holdingMap, results) action = case action of callChoiceV2 @V2.AllocationFactory (holdingMap, results) call (\_cid arg -> do let alloc = arg.allocation - pure (Left (allocationAdmin alloc), alloc.authorizer)) + pure (Left alloc.admin, alloc.authorizer)) (\cid arg inputHoldingCids -> do result <- exercise cid arg with inputHoldingCids - let admin = allocationAdmin arg.allocation + let admin = arg.allocation.admin let newHoldingMap = holdingMapFromList [(ScopedAccount admin arg.allocation.authorizer, result.authorizerChangeCids)] pure (newHoldingMap, TSAR_AllocationInstructionResultV2 result) ) diff --git a/daml/splice-wallet-test/daml/Splice/Scripts/Wallet/TestTokenStandardV2.daml b/daml/splice-wallet-test/daml/Splice/Scripts/Wallet/TestTokenStandardV2.daml index 5141eb1822..fc8b47a7ad 100644 --- a/daml/splice-wallet-test/daml/Splice/Scripts/Wallet/TestTokenStandardV2.daml +++ b/daml/splice-wallet-test/daml/Splice/Scripts/Wallet/TestTokenStandardV2.daml @@ -169,7 +169,7 @@ testTokenStandardAllocateV2 = script do sender = basicAccount sender receiver = basicAccount receiver amount - instrumentId = amuletId + instrumentId = amuletId.id meta = emptyMetadata let aliceLeg = mkTransfer alice bob 100.0 @@ -177,7 +177,7 @@ testTokenStandardAllocateV2 = script do now <- getTime otcTradeCid <- submit venue $ createCmd TradingAppV2.OTCTrade with venue = venue - transferLegs = [aliceLeg] + tradeLegs = [TradingAppV2.TradeLeg registry.dso aliceLeg] createdAt = now settleAt = now `addRelTime` hours 1 settlementDeadline = Some (now `addRelTime` hours 2) @@ -191,7 +191,8 @@ testTokenStandardAllocateV2 = script do reqs <- WalletClientV2.listAllocationRequestsV2 authorizer actor [(installCid, _)] <- query @WalletAppInstall actor forA_ reqs $ \(reqCid, req) -> do - let fundingAmount = negate $ netAmuletCreditAmount registry.dso authorizer req.transferLegs + let [amuletAlloc] = req.allocations + let fundingAmount = negate $ netAmuletCreditAmount authorizer amuletAlloc.transferLegSides inputHoldingCids <- if fundingAmount > 0.0 then WalletClientV2.listUnlockedHoldingCidsFor authorizer actor amuletId else pure [] @@ -199,7 +200,11 @@ testTokenStandardAllocateV2 = script do allocation = Api.Token.AllocationV2.AllocationSpecification with authorizer settlement = req.settlement - transferLegs = req.transferLegs -- no filter in this example, as the test trade affects only amulet + admin = registry.dso + transferLegSides = amuletAlloc.transferLegSides -- no filter in this example, as the test trade affects only amulet + nextIterationFunding = None + committed = False + meta = emptyMetadata inputHoldingCids requestedAt = now extraArgs = emptyExtraArgs diff --git a/daml/splice-wallet/daml/Splice/Wallet/Install.daml b/daml/splice-wallet/daml/Splice/Wallet/Install.daml index 2670e9a3a6..49a88185b2 100644 --- a/daml/splice-wallet/daml/Splice/Wallet/Install.daml +++ b/daml/splice-wallet/daml/Splice/Wallet/Install.daml @@ -680,7 +680,7 @@ template WalletAppInstall controller validatorParty do TokenStandard.Utils.checkActors allocateArg.actors [[endUserParty]] - checkAllocationSpecificationV2 dsoParty allocateArg.allocation endUserParty + checkAllocationSpecificationV2 allocateArg.allocation endUserParty forA_ allocationReqCid $ \reqCid -> do req <- fetchUncheckedButArchiveLater reqCid TokenStandard.Utils.requireMatchExpected @@ -698,7 +698,7 @@ template WalletAppInstall do TokenStandard.Utils.checkActors withdrawArg.actors [[endUserParty]] allocation <- fetchCheckedInterface (ForDso dsoParty) allocationCid - checkAllocationSpecificationV2 dsoParty (view allocation).allocation endUserParty + checkAllocationSpecificationV2 (view allocation).allocation endUserParty exercise allocationCid withdrawArg nonconsuming choice WalletAppInstall_AllocationInstructionV2_Accept : Splice.Api.Token.AllocationInstructionV2.AllocationInstructionResult @@ -709,7 +709,7 @@ template WalletAppInstall do TokenStandard.Utils.checkActors acceptArg.actors [[endUserParty]] allocationInstruction <- fetchCheckedInterface (ForDso dsoParty) allocationInstructionCid - checkAllocationSpecificationV2 dsoParty (view allocationInstruction).allocation endUserParty + checkAllocationSpecificationV2 (view allocationInstruction).allocation endUserParty exercise allocationInstructionCid acceptArg nonconsuming choice WalletAppInstall_AllocationInstructionV2_Withdraw : Splice.Api.Token.AllocationInstructionV2.AllocationInstructionResult @@ -720,7 +720,7 @@ template WalletAppInstall do TokenStandard.Utils.checkActors withdrawArg.actors [[endUserParty]] allocationInstruction <- fetchCheckedInterface (ForDso dsoParty) allocationInstructionCid - checkAllocationSpecificationV2 dsoParty (view allocationInstruction).allocation endUserParty + checkAllocationSpecificationV2 (view allocationInstruction).allocation endUserParty exercise allocationInstructionCid withdrawArg @@ -731,12 +731,11 @@ checkTransferInstructionV2 dsoParty transferInstructionCid actors endUserParty = TokenStandard.Utils.requireMatchExpected ("instruction.transfer.instrumentId", (view instruction).transfer.instrumentId) (amuletInstrumentIdV2 dsoParty) -checkAllocationSpecificationV2 : Party -> Splice.Api.Token.AllocationV2.AllocationSpecification -> Party -> Update () -checkAllocationSpecificationV2 dsoParty allocation endUserParty = do +checkAllocationSpecificationV2 : Splice.Api.Token.AllocationV2.AllocationSpecification -> Party -> Update () +checkAllocationSpecificationV2 allocation endUserParty = do TokenStandard.Utils.requireMatchExpected ("allocation.authorizer", allocation.authorizer) (TokenStandard.Utils.basicAccount endUserParty) - let amuletId = amuletInstrumentIdV2 dsoParty - forA_ allocation.transferLegs $ \leg -> do - TokenStandard.Utils.requireMatchExpected (leg.transferLegId <> ".instrumentId", leg.instrumentId) amuletId + forA_ allocation.transferLegSides $ \leg -> do + TokenStandard.Utils.requireMatchExpected (leg.transferLegId <> ".instrumentId", leg.instrumentId) amuletInstrumentIdName unpackCid : AnyValue -> AnyContractId unpackCid (AV_ContractId cid) = cid diff --git a/docs/gen-daml-docs.sh b/docs/gen-daml-docs.sh index bd00ff3376..743569a833 100755 --- a/docs/gen-daml-docs.sh +++ b/docs/gen-daml-docs.sh @@ -15,7 +15,8 @@ gen_project_docs () ( dpm docs --index-template "$DOCS_DIR/api-templates/$2-index-template.rst" "${DAML_FILES[@]}" --exclude-modules '**.Scripts.**' -f rst -o "$DOCS_DIR/src/app_dev/api/$2" # Workaround for https://github.com/digital-asset/daml/pull/20889/files so we get toctrees again # shellcheck disable=SC2016 - find "$DOCS_DIR/src/app_dev/api/$2" -name '*.rst' -exec sed -i 's/^* :doc:`\(.*\)`$/ \1/g' {} + + find "$DOCS_DIR/src/app_dev/api/$2" -name '*.rst' -exec sed -i.bak -e 's/^\* :doc:`\(.*\)`$/ \1/g' {} + + find "$DOCS_DIR/src/app_dev/api/$2" -name '*.rst.bak' -delete ) # We explicitly exclude from the generated docs API packages that were released and must remain stable (thus are also not compiled any more) diff --git a/token-standard/V2_VALIDATION.md b/token-standard/V2_VALIDATION.md index 5bfa918ad9..b3d3dcca17 100644 --- a/token-standard/V2_VALIDATION.md +++ b/token-standard/V2_VALIDATION.md @@ -44,10 +44,7 @@ It aims to do so by writing Daml script tests that mirror real-world use cases a ## Appendix: Planned Cleanup (Non-blocking) -* Move `TradingAppV2` into its own package to enable reuse in integration tests. -* Merge `splice-token-standard-test-v1` and `splice-token-standard-test-v2` into a single test package, with separate modules for V1 and V2 tests. - -Cleanup performed so far: +Cleanup and improvements applied so far: * Replace `ChoiceExecutionMetadata` with concrete result types for `AllocationRequest_Reject` and `AllocationRequest_Withdraw` choices to prepare for an eventual future where interface definitions @@ -143,3 +140,32 @@ Cleanup performed so far: - validate the API on `TestTokenV2` by adding support for a rich variety of authorization configurations - Use a uniform `AllocationResult` for `Allocation` choices to simplify working with them +- Add iterated settlement support across the V2 API packages so the allocation authorizer + can authorize net funding up front, allow the executors to finalize the + concrete transfer-leg sides at settlement time, and optionally carry reserved funding forward into later settlement + iterations. + - this enables use cases such as prefunding RFQ trades, and funding liquidity pools across multiple settlement iterations. + - `splice-api-token-allocation-request-v2`: + - add `RequestedAllocation` with `admin`, `transferLegSides`, `nextIterationFunding`, `committed`, and `meta` + - change `AllocationRequestView.transferLegs` to `AllocationRequestView.allocations : [RequestedAllocation]` + - move `requestedAt` and `settleAt` onto `AllocationRequestView` + - `splice-api-token-allocation-v2`: + - remove `requestedAt` and `settleAt` from `SettlementInfo` + - change `TransferLeg.instrumentId` from `HoldingV2.InstrumentId` to plain `Text`, and add `TransferSide` / `TransferLegSide` + - extend `AllocationSpecification` with `admin`, `transferLegSides`, `nextIterationFunding`, `committed`, and `meta` + - extend `AllocationView` with `createdAt`, and `numIterations` + - add `FinalizedAllocation` + - extend `Allocation_Settle` with `extraTransferLegSides`, and `nextIterationFunding` + - extend `Allocation_SettleResult` with `nextIterationAllocationCid` + - `splice-api-token-transfer-events-v2` and token-standard utilities: + - switch transfer and holdings-change reporting to `transferLegSides` + - remove `TokenStandardUtils.allocationAdmin`, which is now trivial from `allocation.admin` +- Introduce committed allocations that lock the funds until settlement time. + - `RequestedAllocation.committed` lets an app request creation of a committed allocation. + - `AllocationSpecification.committed` records that commitment on the created allocation. + - if `committed = True`, the authorizer cannot withdraw the allocation before the + settlement deadline; if there is no settlement deadline, the authorizer cannot withdraw it at all. + The allocation must instead be concluded by settlement, cancellation, or registry-specific expiry. +- Remove the `defaultAllocation_*Controllers` helper functions, as the default controller + sets have become straightforward enough to inline in implementations. + diff --git a/token-standard/examples/splice-test-token-v2/daml/Splice/Testing/Tokens/TestTokenV2.daml b/token-standard/examples/splice-test-token-v2/daml/Splice/Testing/Tokens/TestTokenV2.daml index d3aaf20b2d..6437f83e8d 100644 --- a/token-standard/examples/splice-test-token-v2/daml/Splice/Testing/Tokens/TestTokenV2.daml +++ b/token-standard/examples/splice-test-token-v2/daml/Splice/Testing/Tokens/TestTokenV2.daml @@ -62,7 +62,7 @@ template TokenAllocationInstructionV2 with admin : Party requestedAt : Time where - ensure isValidAllocationSpecificationV2 (\instrumentId -> instrumentId.admin == admin) allocation + ensure isValidAllocationSpecificationV2 (const True) allocation signatory fromOptional [] (Map.lookup V2.AIA_Accept actionAuthorizers), admin observer dedupSort (concat (Map.keys availableActions) <> concat (Map.values actionAuthorizers)) @@ -128,13 +128,14 @@ applyAllocationInstructionTransitions this@TokenAllocationInstructionV2{..} oSel meta = emptyMetadata (AIS_Init, AIS_Accepted) -> do - let allocationSpec = allocationStateMachineSpec allocation.authorizer allocation.settlement.executors (allocationAdmin allocation) + let allocationSpec = allocationStateMachineSpec allocation.authorizer allocation.settlement.executors allocation.admin eventLogCid <- getEventLogFromContext extraArgs.context (lockedTokens, authorizerChangeCids) <- splitAndLock eventLogCid allocation inputHoldingCids allocationCid <- create TokenAllocationV2 with originalAllocationCid = None availableActions = mkAvailableActions allocationSpec Map.empty accountMap actionAuthorizers = Map.empty + createdAt = requestedAt .. pure V2.AllocationInstructionResult with output = V2.AllocationInstructionResult_Completed (coerceContractId allocationCid) @@ -163,10 +164,10 @@ template TokenAllocationV2 with lockedTokens : TextMap.TextMap [ContractId V2.Holding] -- ^ Map from instrumentId to holdings. allocation : V2.AllocationSpecification - admin : Party + createdAt : Time where - ensure isValidAllocationSpecificationV2 (\instrumentId -> instrumentId.admin == admin) allocation - signatory accountParties allocation.authorizer, allocationAdmin allocation + ensure isValidAllocationSpecificationV2 (const True) allocation + signatory accountParties allocation.authorizer, allocation.admin observer allocation.settlement.executors interface instance V2.Allocation for TokenAllocationV2 where @@ -178,6 +179,8 @@ template TokenAllocationV2 with allocation holdingCids = Foldable.concat lockedTokens availableActions = allocationV2_availableActionsDefault allocation + createdAt + numIterations = 0 -- TODO(#5464): support iterated settlement in V2 token meta = emptyMetadata -- No extra observers required, as the choice bodies do not introduce further informees. @@ -208,12 +211,12 @@ applyAllocationTransitions applyAllocationTransitions this@TokenAllocationV2{..} oSelf actors action extraArgs = do accountMap <- extractAccountConfigMap - this.admin + this.allocation.admin extraArgs [this.allocation.authorizer] let - mkStateMachine v = allocationStateMachineSpec v.allocation.authorizer v.allocation.settlement.executors (allocationAdmin v.allocation) + mkStateMachine v = allocationStateMachineSpec v.allocation.authorizer v.allocation.settlement.executors v.allocation.admin mkPending newActionAuthorizers newAvailableActions = do pendingInstrCid <- create this with @@ -234,6 +237,7 @@ applyAllocationTransitions this@TokenAllocationV2{..} oSelf actors action extraA transition fromState toState = case (fromState, toState) of (AS_Init, AS_Withdrawn) -> do + ensureWithdrawIsAllowed allocation authorizerHoldingCids <- unlockTokenAllocationV2 "allocation withdrawn" this extraArgs pure V2.AllocationResult with @@ -255,17 +259,18 @@ applyAllocationTransitions this@TokenAllocationV2{..} oSelf actors action extraA assertWithinDeadline "allocation.settlement.settlementDeadline" deadline -- archive the locked tokens - debitAmounts <- fmap (Map.fromListWithR (+)) $ forA (Foldable.concat lockedTokens) $ \cid -> do + debitAmounts <- fmap (TextMap.fromListWithR (+)) $ forA (Foldable.concat lockedTokens) $ \cid -> do token <- fetch cid let holding = view token archive cid require' ("lockedToken", holding.account) isEqualR ("allocation.authorizer", allocation.authorizer) - pure (holding.instrumentId, holding.amount) + require' ("lockedToken.instrumentId.admin", holding.instrumentId.admin) isEqualR ("allocation.admin", allocation.admin) + pure (holding.instrumentId.id, holding.amount) -- prudent engineering: compare debits with expected debits based on the transfer legs, and settle - let creditAmounts = netAllocationCreditAmounts allocation.authorizer allocation.transferLegs + let creditAmounts = netAllocationCreditAmounts allocation.authorizer allocation.transferLegSides let combinedAmounts = mergeAmountMaps debitAmounts creditAmounts - authorizerHoldingCids0 <- forA (Map.toList combinedAmounts) $ \(instrumentId, (debitAmount, creditAmount)) -> do + authorizerHoldingCids0 <- forA (TextMap.toList combinedAmounts) $ \(instrumentId, (debitAmount, creditAmount)) -> do require' ("archived lockedAmulet.amount for " <> show instrumentId, debitAmount) isEqualR @@ -277,13 +282,15 @@ applyAllocationTransitions this@TokenAllocationV2{..} oSelf actors action extraA holding = V2.HoldingView with account = allocation.authorizer amount = creditAmount - instrumentId + instrumentId = V2.InstrumentId with + id = instrumentId + admin = allocation.admin lock = None meta = emptyMetadata - pure (instrumentId.id, [toInterfaceContractId newHoldingCid]) + pure (instrumentId, [toInterfaceContractId newHoldingCid]) else -- No credit of Token ==> no holding change - pure (instrumentId.id, []) + pure (instrumentId, []) -- log v2 transfer events tokenRulesCid <- getEventLogFromContext extraArgs.context @@ -294,7 +301,8 @@ applyAllocationTransitions this@TokenAllocationV2{..} oSelf actors action extraA (concat $ textMapValues authorizerHoldingCids) pure V2.AllocationResult with - output = V2.AllocationResult_Settled + output = V2.AllocationResult_Settled with + nextIterationAllocationCid = None -- TODO(#5464): support iterated settlement in V2 token authorizerHoldingCids = authorizerHoldingCids meta = emptyMetadata -- Note: for V1 tx history we would need create extra choices for @@ -398,7 +406,7 @@ transferInstructionTransition this oSelf actors accountMap extraArgs TIS_Init TI admin = this.transfer.instrumentId.admin account = this.transfer.sender inputHoldingCids = this.transfer.inputHoldingCids - transferLegs = [] + transferLegSides = [] outputHoldingCids = inputHoldingCids <> senderChangeCids observers = accountParties this.transfer.sender extraArgs = ExtraArgs with @@ -666,17 +674,17 @@ splitAndLock : ContractId TransferEventsV2.EventLog -> V2.AllocationSpecificatio splitAndLock eventLogCid allocation inputTokenCids = do -- debit all input amounts - inputAmounts <- Map.fromListWithR (+) . concat <$> forA (TextMap.toList inputTokenCids) (\(_, cids) -> forA cids (\cid -> do + inputAmounts <- TextMap.fromListWithR (+) . concat <$> forA (TextMap.toList inputTokenCids) (\(_, cids) -> forA cids (\cid -> do let tokenCid = coerceContractId cid : ContractId Token token <- fetch tokenCid archive tokenCid - pure (token.holding.instrumentId, token.holding.amount) + pure (token.holding.instrumentId.id, token.holding.amount) )) -- create locked tokens and change for the sender - let creditAmounts = netAllocationCreditAmounts allocation.authorizer allocation.transferLegs + let creditAmounts = netAllocationCreditAmounts allocation.authorizer allocation.transferLegSides let combinedAmounts = mergeAmountMaps inputAmounts creditAmounts - locksAndChange <- forA (Map.toList combinedAmounts) $ \(instrumentId, (inputAmount, creditAmount)) -> do + locksAndChange <- forA (TextMap.toList combinedAmounts) $ \(instrumentId, (inputAmount, creditAmount)) -> do let netAmount = min 0.0 creditAmount + inputAmount when (netAmount < 0.0) $ fail $ T.unlines @@ -690,24 +698,28 @@ splitAndLock eventLogCid allocation inputTokenCids = do holding = V2.HoldingView with account = allocation.authorizer amount = negate creditAmount - instrumentId + instrumentId = V2.InstrumentId with + id = instrumentId + admin = allocation.admin lock = Some $ V2.Lock with - holders = instrumentId.admin :: accountParties allocation.authorizer - expiresAt = Some allocation.settlement.settleAt + holders = allocation.admin :: accountParties allocation.authorizer + expiresAt = allocation.settlement.settlementDeadline expiresAfter = None context = Some $ "allocation for settlement of " <> allocation.settlement.settlementRef.id meta = emptyMetadata - pure [(instrumentId.id, [toInterfaceContractId cid])] + pure [(instrumentId, [toInterfaceContractId cid])] changeTokens <- if netAmount <= 0.0 then pure [] else do cid <- create Token with holding = V2.HoldingView with account = allocation.authorizer amount = netAmount - instrumentId + instrumentId = V2.InstrumentId with + id = instrumentId + admin = allocation.admin lock = None meta = emptyMetadata - pure [(instrumentId.id, [toInterfaceContractId cid])] + pure [(instrumentId, [toInterfaceContractId cid])] pure (lockedTokens, changeTokens) let @@ -716,10 +728,10 @@ splitAndLock eventLogCid allocation inputTokenCids = do -- log the tx history v2 event logNonEmptyChange eventLogCid TransferEventsV2.EventLog_HoldingsChange with - admin = allocationAdmin allocation + admin = allocation.admin account = allocation.authorizer inputHoldingCids = concat (textMapValues inputTokenCids) - transferLegs = [] + transferLegSides = [] outputHoldingCids = concat (textMapValues lockedCids) <> concat (textMapValues authorizerChangeCids) observers = accountParties allocation.authorizer extraArgs = ExtraArgs with @@ -770,10 +782,10 @@ unlockTokenAllocationV2 reason tokenAllocation extraArgs = do holding = holding with lock = None eventLogCid <- getEventLogFromContext extraArgs.context logNonEmptyChange eventLogCid TransferEventsV2.EventLog_HoldingsChange with - admin = allocationAdmin tokenAllocation.allocation + admin = tokenAllocation.allocation.admin account = tokenAllocation.allocation.authorizer inputHoldingCids = concat (textMapValues tokenAllocation.lockedTokens) - transferLegs = [] + transferLegSides = [] outputHoldingCids = concat (textMapValues authorizerCids) observers = accountParties tokenAllocation.allocation.authorizer extraArgs = ExtraArgs with @@ -793,7 +805,7 @@ unlockTokenTransferOffer reason offer extraArgs = do admin = transfer.instrumentId.admin account = transfer.sender inputHoldingCids = transfer.inputHoldingCids - transferLegs = [] + transferLegSides = [] outputHoldingCids = [toInterfaceContractId tokenCid] observers = accountParties transfer.sender extraArgs = ExtraArgs with @@ -801,8 +813,9 @@ unlockTokenTransferOffer reason offer extraArgs = do meta = reasonToMeta reason emptyMetadata return [tokenCid] -mergeAmountMaps : Ord k => Map.Map k Decimal -> Map.Map k Decimal -> Map.Map k (Decimal, Decimal) -mergeAmountMaps = Map.merge (\_ x -> Some (x, 0.0)) (\_ y -> Some (0.0, y)) (\_ x y -> Some (x, y)) + +mergeAmountMaps : TextMap.TextMap Decimal -> TextMap.TextMap Decimal -> TextMap.TextMap (Decimal, Decimal) +mergeAmountMaps = TextMap.merge (\_ x -> Some (x, 0.0)) (\_ y -> Some (0.0, y)) (\_ x y -> Some (x, y)) senderHoldingFromTransfer : V2.Transfer -> V2.HoldingView senderHoldingFromTransfer transfer = V2.HoldingView with diff --git a/token-standard/examples/splice-test-token-v2/daml/Splice/Testing/Tokens/TestTokenV2/AccountConfig.daml b/token-standard/examples/splice-test-token-v2/daml/Splice/Testing/Tokens/TestTokenV2/AccountConfig.daml index ac76fdaf83..002913f5cd 100644 --- a/token-standard/examples/splice-test-token-v2/daml/Splice/Testing/Tokens/TestTokenV2/AccountConfig.daml +++ b/token-standard/examples/splice-test-token-v2/daml/Splice/Testing/Tokens/TestTokenV2/AccountConfig.daml @@ -10,14 +10,12 @@ import Splice.Api.Token.HoldingV2 qualified as V2 import Splice.Api.Token.TransferInstructionV2 qualified as V2 import Splice.Api.Token.AllocationInstructionV2 qualified as V2 import Splice.Api.Token.AllocationV2 qualified as V2 -import Splice.TokenStandard.Utils (checkActors, allocationAdmin, requireMatchExpected, isMemberR, require', accountParties) +import Splice.TokenStandard.Utils (checkActors, requireMatchExpected, isMemberR, require', accountParties) import Splice.Api.Token.MetadataV1 (AnyValue(..), ExtraArgs) import DA.List (dedupSort, dedup, (\\)) -import DA.Assert ((===)) import DA.Optional (whenSome, fromSomeNote, isSome, isNone, fromOptional) import qualified DA.TextMap as TextMap import DA.Action (foldlA) -import DA.Foldable (forA_) import qualified DA.Traversable as Traversable @@ -88,8 +86,8 @@ template AccountConfig assertMsg ("Authorization transfer from " <> show actor <> " to " <> show authorizer <> " is not allowed.") ((actor, authorizer) `elem` authorizationTransfers this) - allocation <- view <$> fetch allocationCid - forA_ allocation.allocation.transferLegs (\leg -> admin === leg.instrumentId.admin) + allocView <- view <$> fetch allocationCid + requireMatchExpected ("allocation.admin", allocView.allocation.admin) admin case action of V2.AA_Withdraw -> exercise allocationCid (V2.Allocation_Withdraw [authorizer] extraArgs) @@ -111,8 +109,8 @@ template AccountConfig assertMsg ("Authorization transfer from " <> show actor <> " to " <> show authorizer <> " is not allowed.") ((actor, authorizer) `elem` authorizationTransfers this) - instr <- view <$> fetch instrCid - forA_ instr.allocation.transferLegs (\leg -> admin === leg.instrumentId.admin) + instrView <- view <$> fetch instrCid + requireMatchExpected ("allocation.admin", instrView.allocation.admin) admin case action of V2.AIA_Accept -> exercise instrCid (V2.AllocationInstruction_Accept [authorizer] extraArgs) @@ -335,7 +333,7 @@ tryUseAccountAuthorityForAA' : tryUseAccountAuthorityForAA' actors action allocationCid authState accountMap extraArgs = do alloc <- view <$> fetch allocationCid let - spec = allocationStateMachineSpec alloc.allocation.authorizer alloc.allocation.settlement.executors (allocationAdmin alloc.allocation) + spec = allocationStateMachineSpec alloc.allocation.authorizer alloc.allocation.settlement.executors alloc.allocation.admin state = getState spec authState oTransition = authorizationTransferFromState actors action state spec authState accountMap diff --git a/token-standard/examples/splice-token-test-trading-app-v2/daml/Splice/Testing/Apps/TradingAppV2.daml b/token-standard/examples/splice-token-test-trading-app-v2/daml/Splice/Testing/Apps/TradingAppV2.daml index 3f9013781d..16a7daa0b1 100644 --- a/token-standard/examples/splice-token-test-trading-app-v2/daml/Splice/Testing/Apps/TradingAppV2.daml +++ b/token-standard/examples/splice-token-test-trading-app-v2/daml/Splice/Testing/Apps/TradingAppV2.daml @@ -29,7 +29,9 @@ import Splice.TokenStandard.Utils template OTCTradeAllocationRequest with authorizer : V2.Account settlement : V2.SettlementInfo - transferLegs : [V2.TransferLeg] + requestedAt : Time + settleAt : Time + allocations : [V2.RequestedAllocation] where signatory settlement.executors observer accountParties authorizer @@ -44,7 +46,9 @@ template OTCTradeAllocationRequest with view = V2.AllocationRequestView with originalRequestCid = None settlement - transferLegs + requestedAt + settleAt = Some settleAt + allocations authorizer availableActions = Map.fromList [ ([p], [V2.ARA_Accept, V2.ARA_Reject]) | p <- accountParties authorizer ] @@ -92,10 +96,28 @@ data SettlementBatch deriving (Eq, Show) +-- | Leg of a trade. +data TradeLeg = TradeLeg with + admin : Party + -- ^ Admin party of the instrument being transferred. + leg : V2.TransferLeg + -- ^ The transfer details. + deriving (Eq, Show) + +splitLegsBy : Ord k => (leg -> k) -> [leg] -> Map.Map k [leg] +splitLegsBy f legs = Map.fromListWithR (<>) [ (f leg, [leg]) | leg <- legs ] + +-- | Group transfer legs by the account that needs to authorize them. +splitTradeLegsByAuthorizer : [TradeLeg] -> Map.Map V2.Account [(Party, V2.TransferLegSide)] +splitTradeLegsByAuthorizer legs = Map.fromListWithR (++) $ do + tradeleg <- legs + (authorizer, leg) <- transferLegSidesWithAuthorizer tradeleg.leg + pure (authorizer, [(tradeleg.admin, leg)]) + -- | A matched trade ready to settle. template OTCTrade with venue : Party - transferLegs : [V2.TransferLeg] -- ^ Transfers to settle. + tradeLegs : [TradeLeg] -- ^ Transfers to settle. createdAt : Time -- ^ When the matched trade was created on-ledger. settleAt : Time -- ^ When the trade should be settled by. settlementDeadline : Optional Time -- ^ Deadline for when the settlement should be completed. @@ -106,12 +128,21 @@ template OTCTrade with with controller venue do - let settlement = mkOtcTradeSettlementInfo self this - forA (Map.toList $ splitLegsByAuthorizer transferLegs) $ \(authorizer, transferLegs) -> do + let settlement = mkOtcTradeSettlementInfoV2 self this + forA (Map.toList $ splitTradeLegsByAuthorizer tradeLegs) $ \(authorizer, authorizerLegs) -> do create OTCTradeAllocationRequest with authorizer settlement - transferLegs + settleAt = settleAt + requestedAt = createdAt + allocations = do + (admin, legs) <- Map.toList $ splitLegsBy (._1) authorizerLegs + pure V2.RequestedAllocation with + admin + transferLegSides = map (._2) legs + nextIterationFunding = None -- Note: this trading app does not make use of iterated settlement + committed = False + meta = emptyMetadata choice OTCTrade_Settle : OTCTrade_SettleResult with @@ -125,8 +156,9 @@ template OTCTrade with assertWithinDeadline "settlementDeadline" deadline -- compute reference data - let settlement = mkOtcTradeSettlementInfo self this - transferLegsByAdmin = splitLegsByAdmin transferLegs + let settlementV2 = mkOtcTradeSettlementInfoV2 self this + let settlementV1 = downcast_v2_v1_SettlementInfo createdAt settleAt settlementV2 + transferLegsByAdmin = splitLegsBy (.admin) tradeLegs -- Archive requests known to be active at this point, so that trader -- wallets won't see the archival of the allocation without a @@ -134,7 +166,7 @@ template OTCTrade with forA_ allocationRequests $ \reqCid -> do req <- fetch reqCid archive reqCid - requireMatchExpected ("AllocationRequest.settlement", req.settlement) settlement + requireMatchExpected ("AllocationRequest.settlement", req.settlement) settlementV2 -- note: we are not checking that there is a request for every traders, as it could be -- there there is a request that got rejected but the allocation still got created. -- @@ -162,16 +194,16 @@ template OTCTrade with -- For V2 settlement, the _SettleBatch choice itself checks that the allocations match the legs result <- exercise factoryCid V2.SettlementFactory_SettleBatch with - settlement - transferLegs - allocationCids = allocationCids <> receiptAllocCids + settlement = settlementV2 + transferLegs = map (.leg) transferLegs + allocations = map nonIteratedAllocation (allocationCids <> receiptAllocCids) actors = [venue] extraArgs = extraArgs pure $ Right (admin, result) Some (SettlementBatchV1 with allocationsWithContext) -> do -- For V1 settlement, we need to check that the allocations match the legs here - let expectedAllocations = expectedTradeAllocationsV1 settlement transferLegs + let expectedAllocations = expectedTradeAllocationsV1 settlementV1 transferLegs let mergedMaps = textMapZip allocationsWithContext expectedAllocations results <- textMapTraverseWithKey mergedMaps \legId (optSettleableAlloc, optExpectedAlloc) -> do let AllocationWithContextV1 {allocationCid, extraArgs, receiverAgreementCid, senderAgreementCid} = @@ -206,8 +238,8 @@ template OTCTrade with allocationRequestCids : [ContractId OTCTradeAllocationRequest] controller venue do - let settlementV2 = mkOtcTradeSettlementInfo self this - let settlementV1 = downcast settlementV2 + let settlementV2 = mkOtcTradeSettlementInfoV2 self this + let settlementV1 = downcast_v2_v1_SettlementInfo createdAt settleAt settlementV2 forA_ allocationRequestCids $ \reqCid -> do req <- fetch reqCid archive reqCid @@ -250,12 +282,10 @@ makeTradeRef tradeCid = V2.Reference with id = "OTCTradeProposal" -- set to the name of the template to simplify debugging cid = Some (coerceContractId tradeCid) -mkOtcTradeSettlementInfo : ContractId OTCTrade -> OTCTrade -> V2.SettlementInfo -mkOtcTradeSettlementInfo otcTradeCid otcTrade = V2.SettlementInfo with +mkOtcTradeSettlementInfoV2 : ContractId OTCTrade -> OTCTrade -> V2.SettlementInfo +mkOtcTradeSettlementInfoV2 otcTradeCid otcTrade = V2.SettlementInfo with executors = [otcTrade.venue] - requestedAt = otcTrade.createdAt settlementRef = makeTradeRef otcTradeCid - settleAt = otcTrade.settleAt meta = emptyMetadata settlementDeadline = otcTrade.settlementDeadline @@ -264,17 +294,15 @@ mkOtcTradeSettlementInfo otcTradeCid otcTrade = V2.SettlementInfo with -- That allocation is created by the sender, and settleed with the authority of sender -- and receiver expectedTradeAllocationsV1 - : V2.SettlementInfo -> [V2.TransferLeg] -> TextMap.TextMap V1.AllocationSpecification -expectedTradeAllocationsV1 settlementInfoV2 transferLegs = + : V1.SettlementInfo -> [TradeLeg] -> TextMap.TextMap V1.AllocationSpecification +expectedTradeAllocationsV1 settlementInfo transferLegs = TextMap.fromList $ do - transferLeg <- transferLegs + leg <- transferLegs let spec = V1.AllocationSpecification with settlement = settlementInfo - transferLegId = transferLeg.transferLegId - transferLeg = downcast transferLeg - pure (transferLeg.transferLegId, spec) - where - settlementInfo = downcast settlementInfoV2 + transferLegId = leg.leg.transferLegId + transferLeg = downcast_v2_v1_TransferLeg leg.admin leg.leg + pure (leg.leg.transferLegId, spec) -- V1 Authorization infrastructure diff --git a/token-standard/splice-api-token-allocation-request-v2/daml/Splice/Api/Token/AllocationRequestV2.daml b/token-standard/splice-api-token-allocation-request-v2/daml/Splice/Api/Token/AllocationRequestV2.daml index b8c2c30571..3683a1f3cc 100644 --- a/token-standard/splice-api-token-allocation-request-v2/daml/Splice/Api/Token/AllocationRequestV2.daml +++ b/token-standard/splice-api-token-allocation-request-v2/daml/Splice/Api/Token/AllocationRequestV2.daml @@ -9,6 +9,7 @@ module Splice.Api.Token.AllocationRequestV2 where import DA.Map qualified as Map +import DA.TextMap qualified as TextMap import Splice.Api.Token.MetadataV1 import Splice.Api.Token.HoldingV2 @@ -36,8 +37,7 @@ interface AllocationRequest where allocationRequest_withdrawExtraObservers : AllocationRequest_Withdraw -> [Party] nonconsuming choice AllocationRequest_Accept : AllocationRequest_AcceptResult - -- ^ Signal to settlement.executors that the requested allocations will be - -- created before `settlement.settleAt`. + -- ^ Signal to settlement.executors that the requested allocations were or will be created. -- -- Wallets MAY call this choice in the same transaction as the creation -- of the requested allocations to protect from creating the same @@ -123,6 +123,29 @@ data AllocationRequestAction -- ^ Additional metadata specific to the action, used for extensibility. deriving (Show, Eq) +-- | An allocation of transfers of instruments maintained by the same asset admin. +data RequestedAllocation = RequestedAllocation with + admin : Party + -- ^ Admin of the instruments being transferred when settling the allocation. + transferLegSides : [TransferLegSide] + -- ^ The transfers to include in the allocation. + -- + -- MAY be empty when requesting an iterated settlement. + nextIterationFunding : Optional (TextMap.TextMap Decimal) + -- ^ Whether the allocation should allow iterated settlement, and if so, + -- the amount of funds to reserve for the next settlement. + -- + -- The amounts are keyed by instrument id and MUST be positive. + committed : Bool + -- ^ Whether to create a committed allocation or not. + -- + -- Committed allocations do not allow the authorizer to withdraw the + -- allocation before the settlement deadline. + meta : Metadata + -- ^ Metadata to attach to the allocation. Used for extensibility; e.g., + -- to attach a reason for the allocation. + deriving (Show, Eq) + -- | View of an `AllocationRequest`. data AllocationRequestView = AllocationRequestView with originalRequestCid : Optional (ContractId AllocationRequest) @@ -139,12 +162,16 @@ data AllocationRequestView = AllocationRequestView with -- user action or is informational only. settlement : SettlementInfo -- ^ Settlement for which allocations are requested to be created. - transferLegs : [TransferLeg] - -- ^ Transfer legs that are requested to be authorized for execution as + allocations : [RequestedAllocation] + -- ^ The allocations that are requested to be authorized for execution as -- part of the settlement. + requestedAt : Time + -- ^ Timestamp at which the request was created. + settleAt : Optional Time + -- ^ Timestamp at which the settlement is expected to be executed. The authorizer + -- SHOULD create their allocations before this time. -- - -- This may or may not be a complete list of transfer legs that are part of the settlement, - -- depending on the confidentiality requirements of the app. + -- For iterated settlements, this is the expected time of the first iteration. availableActions : Map.Map [Party] [AllocationRequestAction] -- ^ The actions that the wallet can present to the user on the allocation request. -- diff --git a/token-standard/splice-api-token-allocation-v2/daml/Splice/Api/Token/AllocationV2.daml b/token-standard/splice-api-token-allocation-v2/daml/Splice/Api/Token/AllocationV2.daml index c6e603da14..49e4ee5c74 100644 --- a/token-standard/splice-api-token-allocation-v2/daml/Splice/Api/Token/AllocationV2.daml +++ b/token-standard/splice-api-token-allocation-v2/daml/Splice/Api/Token/AllocationV2.daml @@ -7,6 +7,9 @@ -- -- - authorizing multiple transfers in a single allocation -- - only allocating the net amount of funds transferred to the allocation +-- - allow the executors to specify the actual transfers after creation of the allocation +-- - use the same allocation for multiple settlement iterations +-- - create committed allocations whose funds are locked until the settlement deadline -- - specifying accounts directly in the transfer legs instead of using metadata -- - flexible actors for extensibility -- - implementation-defined choice observers for view compression where @@ -21,7 +24,7 @@ import DA.Map qualified as Map import DA.TextMap qualified as TextMap import Splice.Api.Token.MetadataV1 -import Splice.Api.Token.HoldingV2 (Holding, InstrumentId, Account) +import Splice.Api.Token.HoldingV2 (Holding, Account) -- Allocation View @@ -50,15 +53,10 @@ data SettlementInfo = SettlementInfo -- -- Executors MUST ensure that they use unique `settlementRef` values for -- different settlements to avoid ambiguity or settlement errors. - requestedAt : Time - -- ^ When the settlement was requested. Provided for display and debugging purposes, - -- but SHOULD be in the past. - settleAt : Time - -- ^ The earliest settlement time. Allocations should be made before this time. Settlement - -- happens at any point after this time. settlementDeadline : Optional Time - -- ^ The latest point at which settlement will occur. This allows registries - -- to set appropriate locks and expiries. + -- The executors' and authorizer's agreed time-to-live for the allocation. + -- After this time, if set, the allocation can no longer be settled, and + -- the authorizer can withdraw the allocation to release the funds. meta : Metadata -- ^ Additional metadata about the settlement, used for extensibility. deriving (Show, Eq) @@ -74,27 +72,76 @@ data TransferLeg = TransferLeg with -- ^ The receiver of the transfer. amount : Decimal -- ^ The amount to transfer. - instrumentId : InstrumentId + instrumentId : Text -- ^ The instrument identifier. meta : Metadata -- ^ Additional metadata about the transfer leg, used for extensibility. deriving (Eq, Ord, Show) --- | An approval by the authorizer to execute the given transfers --- as part of the settlement. --- --- In contrast to an `AllocationView` this just specifies the authorized --- transfers, but not the holdings that fund the transfers. +-- | A side of a transfer. +data TransferSide + = SenderSide + -- ^ The outbound side of a transfer, i.e., the sending of assets. + | ReceiverSide + -- ^ The inbound direction, i.e., the receipt of assets. + deriving (Show, Ord, Eq) + +-- | A side of a transfer leg, which is what allocations authorize. +data TransferLegSide = TransferLegSide with + transferLegId : Text + -- ^ An identifier for the transfer leg. + side : TransferSide + -- ^ The side of the transfer that this leg refers to. + otherside : Account + -- ^ The account on the other side of the transfer leg; i.e., the sender + -- in case of `side == ReceiverSide`, and the receiver in case of + -- `side == SenderSide`. + amount : Decimal + -- ^ The amount being transferred. + instrumentId : Text + -- ^ The instrument identifier. + meta : Metadata + -- ^ Additional metadata about the transfer leg, used for extensibility. + deriving (Eq, Ord, Show) + +-- | An approval by the authorizer to receive or send assets as part of +-- settlement. data AllocationSpecification = AllocationSpecification with settlement : SettlementInfo -- ^ The settlement for which this allocation is made. - transferLegs : [TransferLeg] - -- ^ The transfers whose send or receipt is authorized by this allocation. - -- - -- There MUST be at least one transfer leg. All transfer legs MUST have the - -- same instrument admin and involve the authorizer as sender or receiver. + admin : Party + -- ^ The asset admin of the instruments that are transferred as part of + -- the settlement. authorizer : Account - -- ^ The account authorizing the transfers to happen as part of the settlement. + -- ^ The account authorizing the transfers as part of the settlement. + transferLegSides : [TransferLegSide] + -- ^ The sides of transfer legs authorized by this allocation. + nextIterationFunding : Optional (TextMap.TextMap Decimal) + -- ^ Amounts reserved for funding the next settlement iteration. + -- + -- Amounts are keyed by instrument id and MUST be positive. + -- + -- Setting this to `None` indicates that iterated settlement is disabled, + -- and the allocation can only be settled once with exactly its specified + -- transfer legs. Setting this to an empty map indicates that iterated settlement + -- is enabled, but that no funding for the next iteration is reserved by + -- the authorizer. This can be used when the authorizer expects incoming + -- transfers in the next iteration, and thus does not need to reserve any funding. + committed : Bool + -- ^ Whether the authorizer commits to the allocation until either + -- + -- - the executors settle allocation, + -- - the executors cancel the allocation, + -- - the settlement deadline passed, or + -- - the admin expires the allocation. + -- + -- If set to `True`, then the authorizer cannot withdraw the allocation + -- until the settlement deadline. Use committed allocations for cases + -- where the executors need a guarantee that the allocation will be + -- available until settlement. + meta : Metadata + -- ^ Additional metadata for the allocation specification, which can be used + -- to store information about an allocation used in iterated settlement. deriving (Show, Eq) -- | Actions available on an allocation. @@ -131,9 +178,23 @@ data AllocationView = AllocationView with -- holdings. -- -- MAY be empty for registries that do not represent their holdings on-ledger. + createdAt : Time + -- ^ The time when the allocation was originally created. + numIterations : Int + -- ^ The number of settlement iterations that have been executed for this allocation so far. expiresAt : Optional Time - -- ^ The time at which this allocation expires. SHOULD be as close as the - -- registry can make it to the `allocation.settlement.settlementDeadline`. + -- ^ The time at which the allocation expires if inactive. + -- + -- Registries MAY expire the allocation and return the locked funds to the + -- authorizer after this time. Thereby recovering storage resources and + -- protecting themselves from denial-of-service attacks. + -- + -- TODO(#5487): introduce explicit choice for `Allocation_RefreshExpiry` and mention it here. + -- + -- Registries SHOULD avoid unnecessary refreshes by + -- - making the expiry time as close to the settlement deadline as possible + -- - bumping expiry on every settlement iteration. + -- availableActions : Map.Map [Party] [AllocationAction] -- ^ The actions that the wallet can present to the user on the allocation. -- @@ -150,22 +211,25 @@ data AllocationView = AllocationView with -- Allocation ------------- --- | Default controllers for allocation settlement, which are the `executors`, --- and the instrument admin of the transfer legs. -defaultAllocation_SettleControllers : AllocationView -> [Party] -defaultAllocation_SettleControllers allocView = - allocView.allocation.settlement.executors <> - map (.instrumentId.admin) allocView.allocation.transferLegs - --- | Default controllers for allocation cancellation are the `executors` allocation. -defaultAllocation_CancelControllers : AllocationView -> [Party] -defaultAllocation_CancelControllers allocView = allocView.allocation.settlement.executors - +-- | An allocation finalized by the executors for settlement. +data FinalizedAllocation = FinalizedAllocation with + allocationCid : ContractId Allocation + -- ^ The allocation to settle. + extraTransferLegSides : [TransferLegSide] + -- ^ The extra transfer leg sides to authorize as part of this allocation + -- in this settlement iteration. + -- + -- They MUST be empty unless iterated settlement was enabled by the allocation's authorizer. + nextIterationFunding : Optional (TextMap.TextMap Decimal) + -- ^ The funding to reserve for the next settlement iteration. + -- + -- This MUST NOT be set unless iterated settlement was enabled by the allocation's authorizer. + deriving (Show, Eq) -- | A contract representing the approval of the authorizer to send or receive -- the net amount of assets of the transfer legs as part of a settlement where -- the executors and instrument admin check that every transfer leg has a matching --- authorization from the counterparty. +-- authorization from the otherside. interface Allocation where viewtype AllocationView @@ -191,18 +255,26 @@ interface Allocation where -- ^ Parties executing the settlement. -- -- Implementations MUST check these parties to avoid unauthorized settlement execution. + -- By default, they SHOULD require them to be equal to the allocation + -- `admin` and the `executors`, so that they can jointly guarantee + -- atomic settlement. -- - -- They SHOULD do so by checking that the actors are equal to the - -- `defaultAllocation_SettleControllers`, which ensures maximal - -- compatibility and proper implementation of the constraint: + -- This authorization is typically provided as part of the + -- `SettlementFactory_SettleBatch` choice, which should be used by the + -- `executors` to settle V2 allocations. + extraTransferLegSides : [TransferLegSide] + -- ^ Extra transfer leg sides to settle as part of settlement. -- - -- - The actors MUST always include the settlement executors and the - -- instrument admin of the transfer legs. + -- They MUST NOT be set unless iterated settlement was enabled by the + -- allocation's authorizer. + nextIterationFunding : Optional (TextMap.TextMap Decimal) + -- ^ The funds to reserve for the next settlement iteration, if there is any. -- - -- This authorization is required as all of these parties are expected - -- to guarantee atomic settlement. This authorization is typically provided - -- as part of the `SettlementFactory_SettleBatch` choice, which should - -- be used by the executors to settle V2 allocations. + -- This MUST NOT be set unless iterated settlement was enabled by the + -- allocation's authorizer. + -- + -- Setting this to `None` indicates that no further settlement + -- iterations will be executed after this one. extraArgs : ExtraArgs -- ^ Additional context required in order to exercise the choice. observer allocation_settleExtraObservers this arg @@ -227,14 +299,7 @@ interface Allocation where -- ^ Parties authorizing the cancellation. -- -- Implementations MUST check these parties to avoid unauthorized cancellation. - -- - -- They SHOULD do so by checking that the actors are equal to the - -- `defaultAllocation_CancelControllers`, which ensures maximal - -- compatibility and proper implementation of the following constraints: - -- - -- - The actors MUST always include the settlement executors. - -- - For V1 allocations, the actors MUST additionally include the owners - -- of the sender and receiver accounts of the transfer legs. + -- By default, they SHOULD require them to be equal to the allocation `executors`. extraArgs : ExtraArgs -- ^ Additional context required in order to exercise the choice. observer allocation_cancelExtraObservers this arg @@ -247,6 +312,9 @@ interface Allocation where -- example be used by the authorizer to undo a mistakenly created -- allocation. -- + -- For committed allocations (i.e., `committed` set to `True`), this + -- choice can only be exercised once the settlement deadline has passed. + -- -- The choice is nonconsuming to support alternative consumption patterns, -- e.g., by calling the consuming V1.Allocation_Withdraw choice for -- transaction parsing compatibility. @@ -258,9 +326,8 @@ interface Allocation where -- ^ Parties authorizing the withdrawal. -- -- Implementations MUST check these parties to avoid unauthorized withdrawal. - -- - -- Implementations SHOULD allow the owner and provider of the authorizer - -- account to call this choice to withdraw mistakenly created allocations. + -- By default they SHOULD allow the account parties of the authorizer to + -- withdraw the allocation. extraArgs : ExtraArgs -- ^ Additional context required in order to exercise the choice. observer allocation_withdrawExtraObservers this arg @@ -268,7 +335,6 @@ interface Allocation where do allocation_withdrawImpl this self arg - -- Result Types --------------- @@ -277,11 +343,11 @@ data AllocationResult = AllocationResult with output : AllocationResult_Output -- ^ The output of the action. authorizerHoldingCids : TextMap.TextMap [ContractId Holding] - -- ^ The holdings that were released back to the authorizer keyed by - -- their `instrumentId.id`. + -- ^ New holdings created for the authorizer as part of the settlement + -- keyed by their `instrumentId.id`. meta : Metadata - -- ^ Additional metadata specific to the allocation, used for extensibility. - deriving (Show, Eq) + -- ^ Additional metadata specific to the settlement, used for extensibility. + deriving (Eq, Show) -- | The output of changing the state of an allocation. data AllocationResult_Output @@ -292,11 +358,14 @@ data AllocationResult_Output -- ^ Contract id of the allocation representing the pending state. | AllocationResult_Settled -- ^ The result of settling an allocation by exercising the `Allocation_Settle` choice. + with + nextIterationAllocationCid : Optional (ContractId Allocation) + -- ^ The new allocation created for the next settlement iteration, if any. | AllocationResult_Cancelled -- ^ The result of the `Allocation_Cancel` choice when fully authorized. | AllocationResult_Withdrawn -- ^ The result of the `Allocation_Withdraw` choice when fully authorized. - deriving (Show, Eq) + deriving (Eq, Show) -- Settlement Factory @@ -309,7 +378,7 @@ data SettlementFactoryView = SettlementFactoryView with -- for which this settlement factory can be used. meta : Metadata -- ^ Additional metadata specific to the settlement factory, used for extensibility. - deriving (Show, Eq) + deriving (Eq, Show) -- | A settlement factory enables the net settlement of a batch of allocations -- for the same instrument admin. @@ -327,16 +396,16 @@ interface SettlementFactory where -- The choice is structured in this form for efficiency and privacy. It -- enables the instrument admin to only perform net debits and credits for -- each account across all transfers being settled; and restrict visibility of - -- each credit or debit to executors, admin, and affected account only. + -- each credit or debit to executors, admin, and affected account parties only. with settlement : SettlementInfo -- ^ The settlement for which the allocations are settled. transferLegs : [TransferLeg] - -- ^ The sequence of transfers to execute as part of the settlement. + -- ^ The transfers that are to be executed as part of the settlement. -- -- There MUST be at least one transfer leg. All transfer legs MUST have the -- same instrument admin as the one of the factory. - allocationCids : [ContractId Allocation] + allocations : [FinalizedAllocation] -- ^ Allocations to settle. -- -- They serve as proof that all transfers executed as part of settlement @@ -360,9 +429,8 @@ interface SettlementFactory where -- can be used for implementation specific authorization patterns. -- -- Implementations MUST check this value to avoid unauthorized settlement execution. - -- - -- Implementations SHOULD allow the `settlement.executors` to execute the settlement - -- to maximize compatibility with apps. + -- By default the SHOULD check that they are equal to + -- `settlement.executors` to provide maximal compatibility with apps. extraArgs : ExtraArgs -- ^ Additional choice arguments. observer settlementFactory_settleBatchExtraObservers this arg @@ -382,10 +450,9 @@ interface SettlementFactory where -- | Result of settling a batch of allocations. data SettlementFactory_SettleBatchResult = SettlementFactory_SettleBatchResult with - newHoldingCids : Map.Map Account (TextMap.TextMap [ContractId Holding]) - -- ^ New holdings created as part of the batch settlement, grouped by account and - -- keyed by their `instrumentId.id` to enable chaining of actions on holdings of - -- one or more accounts. + allocationSettleResults : [AllocationResult] + -- ^ The result of settling each allocation in the batch. + -- In the same order as the `allocationCids` in the choice arguments. meta : Metadata -- ^ Additional metadata specific to the batch settlement, used for extensibility. deriving (Eq, Show) diff --git a/token-standard/splice-api-token-transfer-events-v2/daml/Splice/Api/Token/TransferEventsV2.daml b/token-standard/splice-api-token-transfer-events-v2/daml/Splice/Api/Token/TransferEventsV2.daml index 5a9362a691..3c43cf201c 100644 --- a/token-standard/splice-api-token-transfer-events-v2/daml/Splice/Api/Token/TransferEventsV2.daml +++ b/token-standard/splice-api-token-transfer-events-v2/daml/Splice/Api/Token/TransferEventsV2.daml @@ -7,25 +7,35 @@ module Splice.Api.Token.TransferEventsV2 where import Splice.Api.Token.MetadataV1 import Splice.Api.Token.HoldingV2 --- Note: the TransferLeg type below is identical to Allocation.TransferLeg, but +-- Note: the TransferLegSide type below is identical to Allocation.TransferLegSide, but -- defined separately to avoid a tight coupling between how to specify -- allocations and how to report transfers. This allows evolving these two APIs -- independently. --- | A specification of a transfer of holdings between two parties. -data TransferLeg = TransferLeg with +-- | A side of a transfer. +data TransferSide + = SenderSide + -- ^ The outbound side of a transfer, i.e., the sending of assets. + | ReceiverSide + -- ^ The inbound direction, i.e., the receipt of assets. + deriving (Show, Ord, Eq) + +-- | A side of a transfer of holdings between two parties. +-- +-- Used to report transfer events on the affected accounts. +data TransferLegSide = TransferLegSide with transferLegId : Text - -- ^ An identifier for the transfer leg intended to disambiguate multiple - -- transfer legs reported in the same event. - -- - -- Use the empty string as the default identifier if no disambiguation is needed. - sender : Account - -- ^ The sender of the transfer. - receiver : Account - -- ^ The receiver of the transfer. + -- ^ An identifier for the transfer leg intended to correlate the sender + -- and receiver sides. + side : TransferSide + -- ^ The side of the transfer that this leg refers to. + otherside : Account + -- ^ The account on the other side of the transfer leg; i.e., the sender + -- in case of `side == ReceiverSide`, and the receiver in case of + -- `side == SenderSide`. amount : Decimal -- ^ The amount to transfer. - instrumentId : InstrumentId + instrumentId : Text -- ^ The instrument identifier. meta : Metadata -- ^ Additional metadata about the transfer leg, used for extensibility. @@ -68,12 +78,12 @@ interface EventLog where -- Note that these MAY include holdings that were created and archived -- within the same transaction. Such holding contracts-ids will occur -- in both the input and output list of holdings. - transferLegs : [TransferLeg] + transferLegSides : [TransferLegSide] -- ^ The transfers that caused the change in holdings. -- -- Their net balance changes MUST match the change in holdings of the account. - -- Transfers between accounts MUST be reported for both sender and receiver separately. - -- Transfer leg ids MUST be unique. + -- Both sides of a transfer MUST be reported, and their ids must match. + -- Transfer leg ids MUST be unique for different transfer legs. -- -- Note that the settlement of transfers whose net balance change is -- zero MAY result in events that do have empty input and output @@ -81,7 +91,7 @@ interface EventLog where -- -- Note also that merging and splitting of holdings MAY be reported with -- an empty list of transfer legs, as the merge and split actions may not - -- related to a (self-)transfer of tokens. + -- be related to a (self-)transfer of tokens. outputHoldingCids : [ContractId Holding] -- ^ The newly created holdings of the account. observers : [Party] diff --git a/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/Apps/TradingAppV2_Backend.daml b/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/Apps/TradingAppV2_Backend.daml index 059776e023..0922da41cb 100644 --- a/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/Apps/TradingAppV2_Backend.daml +++ b/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/Apps/TradingAppV2_Backend.daml @@ -27,7 +27,6 @@ import DA.TextMap qualified as TextMap import Splice.Api.Token.MetadataV1 import Splice.Api.Token.AllocationV1 qualified as V1 -import Splice.Api.Token.HoldingV2 qualified as V2 import Splice.Api.Token.AllocationV2 qualified as V2 import Splice.Api.Token.AllocationInstructionV2 qualified as V2 import Splice.Api.Token.TransferEventsV2 qualified as TransferEventsV2 @@ -63,14 +62,6 @@ data SettleableTrade = SettleableTrade with batchesByAdmin : Map Party SettlementBatch allocationRequestCids : [ContractId OTCTradeAllocationRequest] --- | Helper type of a transfer leg together with its authorizer. -type AuthorizedLeg = (V2.Account, V2.TransferLeg) - -toAuthorizedLegs : V2.TransferLeg -> [AuthorizedLeg] -toAuthorizedLegs leg - | leg.sender == leg.receiver = [(leg.sender, leg)] -- self-transfers are authorized only once - | otherwise = [(leg.sender, leg), (leg.receiver, leg)] - groupAsMapBy : Ord k => (a -> k) -> [a] -> Map k [a] groupAsMapBy k xs = Map.fromListWithR (++) [ (k x, [x]) | x <- xs ] @@ -116,25 +107,25 @@ querySettleableTrades registries venue = do let otcTrade = trade.otcTrade let receiversWithAgreements = map basicAccount (Map.keys agreementsByTrader) let minimallyRequiredAuthorizations = do - authz@(authorizer, leg) <- concatMap toAuthorizedLegs otcTrade.transferLegs + (authorizer, leg) <- concatMap (\tradeleg -> transferLegSidesWithAuthorizer tradeleg.leg) otcTrade.tradeLegs -- the venue can create their own allocations directly as part of settlement guard (authorizer.owner /= venue) - -- authorization is only required for senders, and for receivers without trade agreements - guard (leg.sender == authorizer || authorizer `notElem` receiversWithAgreements) - pure authz + -- authorization is only required for outgoing transfers, and for receivers without trade agreements + guard (leg.side == V2.SenderSide || authorizer `notElem` receiversWithAgreements) + pure (authorizer, leg) let actualAuthorizations = do (_cid, allocView) <- trade.allocationsV2 - leg <- allocView.allocation.transferLegs + leg <- allocView.allocation.transferLegSides pure (allocView.allocation.authorizer, leg) <> do (_cid, allocView) <- trade.allocationsV1 let legV1 = allocView.allocation.transferLeg let legV2 = upcast_v1_v2_TransferLeg allocView.allocation.transferLegId legV1 - -- V1 allocations cover both sender and receiver authorization - [(basicAccount legV1.sender, legV2), (basicAccount legV1.receiver, legV2)] + -- V1 allocations authorize both sides of the transfer + transferLegSidesWithAuthorizer legV2 if Set.fromList minimallyRequiredAuthorizations `Set.isSubsetOf` Set.fromList actualAuthorizations then do @@ -144,8 +135,8 @@ querySettleableTrades registries venue = do mkSettleableTrade trade agreementsByTrader = do let otcTrade = trade.otcTrade let otcTradeCid = trade.otcTradeCid - let settlement = mkOtcTradeSettlementInfo otcTradeCid otcTrade - let transferLegsByAdmin = splitLegsByAdmin otcTrade.transferLegs + let settlement = mkOtcTradeSettlementInfoV2 otcTradeCid otcTrade + let transferLegsByAdmin = splitLegsBy (.admin) otcTrade.tradeLegs -- create V1 contexts let allocationsV1ByAdmin = groupAsMapBy (._2.allocation.transferLeg.instrumentId.admin) trade.allocationsV1 @@ -172,7 +163,7 @@ querySettleableTrades registries venue = do pure ((admin, batch), disclosures) -- create V2 contexts - let allocationsV2ByAdmin = groupAsMapBy (\(_, allocView) -> allocationAdmin allocView.allocation) trade.allocationsV2 + let allocationsV2ByAdmin = groupAsMapBy (\(_, allocView) -> allocView.allocation.admin) trade.allocationsV2 settleBatchContextsV2 <- forA (Map.toList transferLegsByAdmin) $ \(admin, requiredLegs) -> do supportsV2 <- MultiRegistry.supportsV2 registries admin @@ -183,9 +174,9 @@ querySettleableTrades registries venue = do let existingAllocations = fromOptional [] $ Map.lookup admin allocationsV2ByAdmin let actualAuthorizations = Set.fromList $ do (_, allocView) <- existingAllocations - leg <- allocView.allocation.transferLegs + leg <- allocView.allocation.transferLegSides pure (allocView.allocation.authorizer, leg) - let requiredAuthorizations = Set.fromList $ concatMap toAuthorizedLegs requiredLegs + let requiredAuthorizations = Set.fromList $ concatMap (\tradeleg -> transferLegSidesWithAuthorizer tradeleg.leg) requiredLegs let missingAuthorizations = requiredAuthorizations `Set.difference` actualAuthorizations let missingByAuthorizer = groupAsMapBy fst $ Set.toList missingAuthorizations missingAllocations <- forA (Map.toList missingByAuthorizer) $ \(authorizer, missingLegs) -> do @@ -193,9 +184,13 @@ querySettleableTrades registries venue = do fail $ "unexpected internal failure: missing authorizer is a non-basic account " <> show authorizer enrichedChoice <- RegistryApiV2.getAllocationFactory registry V2.AllocationFactory_Allocate with allocation = V2.AllocationSpecification with - transferLegs = map snd missingLegs + admin + transferLegSides = map snd missingLegs settlement + nextIterationFunding = None + committed = False authorizer + meta = emptyMetadata inputHoldingCids = [] requestedAt = now extraArgs = emptyExtraArgs @@ -208,8 +203,8 @@ querySettleableTrades registries venue = do let allocationCids = map fst existingAllocations let settleBatchArg = V2.SettlementFactory_SettleBatch with settlement - transferLegs = requiredLegs - allocationCids + transferLegs = map (.leg) requiredLegs + allocations = map (nonIteratedAllocation . fst) existingAllocations extraArgs = emptyExtraArgs actors = [venue] context <- RegistryApiV2.getSettlementFactory registry settleBatchArg @@ -262,7 +257,7 @@ cancelWithTradeAgreements registries trade = do pure (allocationWithContext, context.disclosures) -- grab contexts for V2 allocation cancellation allocationsWithContextV2 <- forA trade.allocationsV2 $ \(allocCid, allocView) -> do - registry <- MultiRegistry.getRegistryApiV2 registries (allocationAdmin allocView.allocation) + registry <- MultiRegistry.getRegistryApiV2 registries allocView.allocation.admin context <- RegistryApiV2.getAllocation_CancelContext registry allocCid emptyMetadata let extraArgs = ExtraArgs with context = context.choiceContext diff --git a/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/Registries/AmuletRegistryV2.daml b/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/Registries/AmuletRegistryV2.daml index 8a89039e6c..3825625f35 100644 --- a/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/Registries/AmuletRegistryV2.daml +++ b/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/Registries/AmuletRegistryV2.daml @@ -270,8 +270,8 @@ registryApi_getSettlementFactory registry arg = do -- Option 2 seems preferable from given an architecture where the backend does not -- ingest interfaces, but only works with the concrete templates. -- - lockedDs <- forA arg.allocationCids $ \allocCid -> do - getLockedAmuletForAllocationsD registry allocCid + lockedDs <- forA arg.allocations $ \alloc -> do + getLockedAmuletForAllocationsD registry alloc.allocationCid let fullContext = withExtraDisclosures (extAmuletRulesD <> mconcat lockedDs) transferC pure EnrichedFactoryChoice with factoryCid = toInterfaceContractId @SettlementFactory extAmuletRulesCid diff --git a/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/Registries/TestTokenV2_RegistryV2.daml b/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/Registries/TestTokenV2_RegistryV2.daml index 3751155b75..129de9c9b1 100644 --- a/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/Registries/TestTokenV2_RegistryV2.daml +++ b/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/Registries/TestTokenV2_RegistryV2.daml @@ -158,8 +158,8 @@ registryApi_getSettlementFactory registryApi_getSettlementFactory registry arg = do let accounts = dedup $ concatMap (\leg -> [leg.sender, leg.receiver]) arg.transferLegs (tokenRulesCid, context, disclosures) <- getAccountMapAndTokenRulesC registry accounts - lockedDs <- forA arg.allocationCids $ \allocCid -> do - getLockedTokensForAllocationsD registry allocCid + lockedDs <- forA arg.allocations $ \alloc -> do + getLockedTokensForAllocationsD registry alloc.allocationCid let arg' = arg with diff --git a/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/TokenStandard/WalletClientV2.daml b/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/TokenStandard/WalletClientV2.daml index 5c4721d82b..27e0243bc2 100644 --- a/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/TokenStandard/WalletClientV2.daml +++ b/token-standard/splice-token-standard-test-v2/daml/Splice/Testing/TokenStandard/WalletClientV2.daml @@ -14,6 +14,7 @@ module Splice.Testing.TokenStandard.WalletClientV2 listUnlockedHoldingCidsFor, checkHoldingWithAmountExists, + checkUnlockedBalance, checkBalanceBounds, checkBalance, checkAccountBalance, @@ -56,6 +57,8 @@ module Splice.Testing.TokenStandard.WalletClientV2 createBatchingUtility, -- ** Allocations + extractNextIterationAllocationCid, + mkAllocationFactory_AllocateV1, mkAllocationFactory_AllocateV2, mkAllocationRequest_AcceptV2, @@ -77,13 +80,14 @@ module Splice.Testing.TokenStandard.WalletClientV2 mkTransferFactory_TransferV2', -- * Tx history V2 checking utilities + submitAndCheckTxHistory, extractAndCheckTxHistory, expectTxHistoryReasons, ) where import DA.Action (unless, when) import DA.Foldable (forA_) -import DA.List (dedupSort, sort, unique) +import DA.List (dedupSort, dedupOn, sort, unique) import DA.Map qualified as Map import DA.Optional import DA.Time @@ -114,7 +118,6 @@ import Splice.Testing.TokenStandard.WalletClient qualified as WalletClientV1 import Splice.Testing.Utils import Daml.Script -import Splice.TokenStandard.Utils (allocationAdmin) -- | List all holdings of a specific instrument owned by a party. listHoldings : Party -> V2.InstrumentId -> Script [(ContractId V2.Holding, V2.HoldingView)] @@ -214,9 +217,20 @@ getAccountBalance actor account instrumentId = do guard (holding.account == account && holding.instrumentId == instrumentId) pure holding.amount +checkUnlockedBalance : Party -> V2.InstrumentId -> Decimal -> Script () +checkUnlockedBalance p instrumentId balance = do + holdings <- listHoldings p instrumentId + let unlockedHoldings = filter (\(_, holding) -> isNone holding.lock) holdings + let total = sum $ map (._2.amount) unlockedHoldings + unless (total == balance) $ fail $ T.unlines [ + "Wallet " <> show p <> ": unlocked balance of " <> show total <> " for " <> show instrumentId <> + " does not match the expected balance of " <> show balance, + "All holdings: " <> show holdings, + "Unlocked holdings: " <> show unlockedHoldings ] + + -- Transfer offers ------------------ - -- | List pending V2 transfer offers (as sender or receiver) listTransferOffersV2 : Party -> V2.InstrumentId -> Script [(ContractId V2.TransferInstruction, V2.TransferInstructionView)] listTransferOffersV2 p instrumentId = do @@ -303,22 +317,17 @@ extractPendingTransferInstructionV2 result = error $ "Expected a TransferInstruc -- Allocations -------------- --- | List all allocations requested from the actor for instruments by the admin. +-- | List all allocations requested from the account for instruments by the admin. listRequestedAllocationsForAdmin : Party -> V2.Account -> Party -> Script [V2.AllocationSpecification] listRequestedAllocationsForAdmin admin tradingAccount actor = do reqs <- queryInterface @V2.AllocationRequest actor - let amuletAllocs = do + let allocSpecs = do (_reqCid, Some req) <- reqs - let transferLegs = do - tf <- req.transferLegs - guard (tf.instrumentId.admin == admin) - guard (tradingAccount == tf.sender || tradingAccount == tf.receiver) - return tf - pure V2.AllocationSpecification with - authorizer = tradingAccount - settlement = req.settlement - transferLegs - pure amuletAllocs + guard (req.authorizer == tradingAccount) + alloc <- req.allocations + guard (alloc.admin == admin) + pure $ TSU.requestedAllocationToSpecification req.settlement req.authorizer alloc + pure allocSpecs listAllocationsV2 : V2.Account -> Party -> Script [(ContractId V2.Allocation, V2.AllocationView)] listAllocationsV2 tradingAccount actor = do @@ -340,6 +349,12 @@ listAllocationRequestsV2 tradingAccount actor = do reqs <- queryInterface @V2.AllocationRequest actor return [ (reqCid, reqView) | (reqCid, Some reqView) <- reqs, tradingAccount == reqView.authorizer ] +extractNextIterationAllocationCid : V2.AllocationResult -> Optional (ContractId V2.Allocation) +extractNextIterationAllocationCid allocResult = + case allocResult.output of + V2.AllocationResult_Settled with nextIterationAllocationCid -> nextIterationAllocationCid + _ -> None + -- | Simulate a V2 wallet accepting an allocation request from a V1 app. acceptAllocationRequestV1 : MultiRegistry.MultiRegistry -> Party -> V1.AllocationRequestView @@ -362,7 +377,7 @@ acceptAllocationInstructionV2 : MultiRegistry.MultiRegistry -> Party -> (ContractId V2.AllocationInstruction, V2.AllocationInstructionView) -> Script V2.AllocationInstructionResult acceptAllocationInstructionV2 registries actor (instrCid, instrView) = do - registry <- MultiRegistry.getRegistryApiV2 registries (allocationAdmin instrView.allocation) + registry <- MultiRegistry.getRegistryApiV2 registries instrView.allocation.admin context <- V2.getAllocationInstruction_AcceptContext registry instrCid emptyMetadata (result, _change) <- extractAndCheckTxHistory (Map.keys registries) $ submitResultAndTree (actAs actor <> discloseMany' context.disclosures) $ @@ -380,7 +395,7 @@ withdrawAllocationInstructionV2 : MultiRegistry.MultiRegistry -> Party -> (ContractId V2.AllocationInstruction, V2.AllocationInstructionView) -> Script (Optional (ContractId V2.AllocationInstruction)) withdrawAllocationInstructionV2 registries actor (instrCid, instrView) = do - registry <- MultiRegistry.getRegistryApiV2 registries (allocationAdmin instrView.allocation) + registry <- MultiRegistry.getRegistryApiV2 registries instrView.allocation.admin context <- V2.getAllocationInstruction_WithdrawContext registry instrCid emptyMetadata (res, _change) <- extractAndCheckTxHistory (Map.keys registries) $ @@ -401,7 +416,7 @@ withdrawAllocationV2 : MultiRegistry.MultiRegistry -> Party -> (ContractId V2.Allocation, V2.AllocationView) -> Script (Optional (ContractId V2.Allocation)) withdrawAllocationV2 registries actor (allocCid, allocView) = do - registry <- MultiRegistry.getRegistryApiV2 registries (allocationAdmin allocView.allocation) + registry <- MultiRegistry.getRegistryApiV2 registries allocView.allocation.admin context <- V2.getAllocation_WithdrawContext registry allocCid emptyMetadata (res, _change) <- extractAndCheckTxHistory (Map.keys registries) $ @@ -587,8 +602,12 @@ mkAllocationFactory_AllocateV2 registryV2 actor allocation = do arg = enrichedChoice.arg pure TSABatch with actions = [action] - accountsAndInstruments = Set.fromList - [ (allocation.authorizer, leg.instrumentId) | leg <- allocation.transferLegs ] + accountsAndInstruments = Set.fromList $ do + leg <- allocation.transferLegSides + let iid = V2.InstrumentId with + id = leg.instrumentId + admin = allocation.admin + pure (allocation.authorizer, iid) disclosures = enrichedChoice.disclosures mkAllocationRequest_AcceptV2 : Party -> ContractId V2.AllocationRequest -> Script TSABatch @@ -625,26 +644,31 @@ mkAcceptAllocationRequestV2 : MultiRegistry.MultiRegistry -> Party -> (ContractId V2.AllocationRequest, V2.AllocationRequestView) -> Script TSABatch mkAcceptAllocationRequestV2 registries actor (reqCid, req) = do acceptRequestBatch <- mkAllocationRequest_AcceptV2 actor reqCid - let legsByAdmin = TSU.splitLegsByAdmin req.transferLegs - batches <- forA (Map.toList legsByAdmin) $ \(admin, legs) -> do - apis <- MultiRegistry.getRegistryApis registries admin + batches <- forA req.allocations $ \alloc -> do + apis <- MultiRegistry.getRegistryApis registries alloc.admin case (apis.v2Api, apis.v1Api) of (Some registryV2, _) -> do -- when possible use the V2 factory mkAllocationFactory_AllocateV2 registryV2 actor V2.AllocationSpecification with + admin = alloc.admin authorizer = req.authorizer settlement = req.settlement - transferLegs = legs + transferLegSides = alloc.transferLegSides + nextIterationFunding = alloc.nextIterationFunding + committed = alloc.committed + meta = alloc.meta (None, Some _) -> do -- allocate legs for V1-only admins via their V1 factory - let reqV1 = V1.AllocationRequestView with - settlement = TSU.downcast req.settlement - transferLegs = TextMap.fromList [(leg.transferLegId, TSU.downcast leg) | leg <- legs] - meta = emptyMetadata + let reqV1 = (TSU.downcast req) with + -- overwrite the transfer legs to only include the legs relevant for this admin + transferLegs = TextMap.fromList $ do + leg <- alloc.transferLegSides + let legV2 = TSU.transferLegFromSide req.authorizer leg + pure (leg.transferLegId, TSU.downcast_v2_v1_TransferLeg alloc.admin legV2) mkAcceptAllocationRequestV1 registries actor reqV1 - (None, None) -> fail $ "unknown registry admin " <> show admin + (None, None) -> fail $ "unknown registry admin " <> show alloc.admin pure (acceptRequestBatch <> mconcat batches) @@ -713,6 +737,11 @@ mkTransferFactory_TransferV2' registries t = do type HoldingSnapshot = Map.Map (ContractId V2.Holding) V2.HoldingView +submitAndCheckTxHistory : [Party] -> SubmitOptions -> Commands b -> Script b +submitAndCheckTxHistory adminParties submitOptions action = do + (result, _changes) <- extractAndCheckTxHistory adminParties $ submitResultAndTree submitOptions action + pure result + extractAndCheckTxHistory : [Party] -> Script (a, TransactionTree) -> Script (a, [TransferEventsV2.EventLog_HoldingsChange]) extractAndCheckTxHistory adminParties action = do holdingsBefore <- snapshotHoldings adminParties @@ -732,7 +761,8 @@ expectTxHistoryReasons changes expectedReasons = do where actualReasons = sort $ do change <- mergeHoldingChangesByAdminAndAccount changes - let legReasons = [ TSU.reasonFromMeta leg.meta | leg <- change.transferLegs ] + -- dedup on transferLegId to avoid duplicate reasons for self-transfers + let legReasons = [ TSU.reasonFromMeta leg.meta | leg <- dedupOn (.transferLegId) change.transferLegSides ] pure (change.account, legReasons, TSU.reasonFromMeta change.extraArgs.meta) snapshotHoldings : [Party] -> Script HoldingSnapshot @@ -751,7 +781,7 @@ validateHoldingsChanges holdingsBefore txTree holdingsAfter = do fail $ "duplicate output holding cids across changes: " <> show allOutputHoldingCids -- check invariants of individual holding changes forA_ changes $ \change -> do - when (null change.transferLegs && null change.inputHoldingCids && null change.outputHoldingCids) $ + when (null change.transferLegSides && null change.inputHoldingCids && null change.outputHoldingCids) $ fail $ "holding change is missing transfer legs or input/output holding cids: " <> show change -- check invariants of merged changes per account outputHoldings <- forA mergedChanges validateMergedHoldingsChange @@ -767,10 +797,20 @@ validateHoldingsChanges holdingsBefore txTree holdingsAfter = do ] -- check that transfer legs are reported for both sides of the transfer for all non-admin accounts forA_ changes $ \change -> do - forA_ change.transferLegs $ \leg -> do - forA_ [leg.sender, leg.receiver] $ \legAccount -> - unless ((legAccount, leg) `elem` allNonAdminTransferLegs || legAccount.owner == change.admin) $ - fail $ "transfer leg " <> show leg <> " is not reported for both sender and receiver" + forA_ change.transferLegSides $ \leg -> do + -- check that the direction of burn accounts is right + when (leg.otherside == TSU.burnAccount change.admin && leg.side == TransferEventsV2.ReceiverSide) $ + fail $ "cannot receiver from a burn account: " <> show leg + when (leg.otherside == TSU.mintAccount change.admin && leg.side == TransferEventsV2.SenderSide) $ + fail $ "cannot send to a mint account: " <> show leg + -- check that the other side of the transfer leg is also reported + let otherSide = case leg.side of + TransferEventsV2.SenderSide -> TransferEventsV2.ReceiverSide + TransferEventsV2.ReceiverSide -> TransferEventsV2.SenderSide + let othersideLeg = + (leg.otherside, leg with side = otherSide; otherside = change.account) + unless (othersideLeg `elem` allNonAdminTransferLegs || othersideLeg._1.owner == change.admin) $ + fail $ "transfer leg side " <> show leg <> " is not reported" -- check that total supply changes are reported via burns and mints unless (reportedSupplyChangeByAdmin == actualSupplyChangeByAdmin) $ fail $ T.unlines @@ -786,15 +826,19 @@ validateHoldingsChanges holdingsBefore txTree holdingsAfter = do allInputHoldingCids = sort $ concatMap (.inputHoldingCids) changes allOutputHoldingCids = sort $ concatMap (.outputHoldingCids) changes - mintOrBurnLegs = dedupSort $ do - change <- changes - leg <- change.transferLegs - guard (leg.receiver == TSU.burnAccount change.admin || leg.sender == TSU.mintAccount change.admin) - pure leg reportedSupplyChangeByAdmin = Map.filter (/= 0.0) $ Map.fromListWithR (+) $ do - leg <- mintOrBurnLegs - let amount = if leg.receiver == TSU.burnAccount leg.instrumentId.admin then negate leg.amount else leg.amount - pure (leg.instrumentId.admin, amount) + change <- changes + -- ignore changes reported on burn and mint accounts since they are not real accounts + guard (change.account /= TSU.burnAccount change.admin && change.account /= TSU.mintAccount change.admin) + leg <- change.transferLegSides + case leg.side of + TransferEventsV2.SenderSide -> do + guard (leg.otherside == TSU.burnAccount change.admin) + pure (change.admin, negate leg.amount) + TransferEventsV2.ReceiverSide -> do + guard (leg.otherside == TSU.mintAccount change.admin) + pure (change.admin, leg.amount) + actualSupplyChangeByAdmin = Map.filter (/= 0.0) $ Map.fromListWithR (+) $ [ (holding.instrumentId.admin, negate holding.amount) | (_, holding) <- Map.toList holdingsBefore ] ++ [ (holding.instrumentId.admin, holding.amount) | (_, holding) <- Map.toList holdingsAfter ] @@ -802,17 +846,13 @@ validateHoldingsChanges holdingsBefore txTree holdingsAfter = do allNonAdminTransferLegs = do change <- changes guard (change.account.owner /= change.admin) -- admins are free to not provide tx history for their accounts - leg <- change.transferLegs + leg <- change.transferLegSides pure (change.account, leg) validateMergedHoldingsChange change | not (unique change.inputHoldingCids) = fail $ "duplicate input holding cids in change: " <> show change | not (unique change.outputHoldingCids) = fail $ "duplicate output holding cids in change: " <> show change | otherwise = do - -- check that the transfer legs affect the account - forA_ change.transferLegs $ \leg -> do - unless (leg.sender == change.account || leg.receiver == change.account) $ - fail $ "holding change includes transfer leg that does not affect the account: " <> show leg -- check input and output holdings are transient or can be read in the expected snapshots let checkHolding cid holding | holding.account /= change.account = @@ -842,7 +882,7 @@ validateHoldingsChanges holdingsBefore txTree holdingsAfter = do let (credits, outputHoldings) = unzip (catOptionals creditsAndOutputs) let ignoreZeroChanges = Map.filter (/= 0.0) let holdingAmountChanges = ignoreZeroChanges $ Map.fromListWithR (+) (catOptionals debits ++ credits) - let legAmountChanges = ignoreZeroChanges $ netAllocationCreditAmountsOfLegs change.account change.transferLegs + let legAmountChanges = ignoreZeroChanges $ netAllocationCreditAmountsOfLegs change.admin change.transferLegSides unless (holdingAmountChanges == legAmountChanges || change.account.owner == change.admin) $ fail $ T.unlines [ "holding amount changes do not match leg amount changes for account " <> show change.account @@ -852,14 +892,15 @@ validateHoldingsChanges holdingsBefore txTree holdingsAfter = do ] pure outputHoldings -netAllocationCreditAmountsOfLegs : V2.Account -> [TransferEventsV2.TransferLeg] -> Map.Map V2.InstrumentId Decimal -netAllocationCreditAmountsOfLegs authorizer transferLegs = +netAllocationCreditAmountsOfLegs : Party -> [TransferEventsV2.TransferLegSide] -> Map.Map V2.InstrumentId Decimal +netAllocationCreditAmountsOfLegs admin transferLegs = Map.fromListWithR (+) $ map legAmount transferLegs where legAmount leg = - ( leg.instrumentId - , (if leg.sender == authorizer then negate leg.amount else 0.0) + - (if leg.receiver == authorizer then leg.amount else 0.0) + ( V2.InstrumentId with id = leg.instrumentId; admin + , case leg.side of + TransferEventsV2.SenderSide -> negate leg.amount + TransferEventsV2.ReceiverSide -> leg.amount ) mergeHoldingChangesByAdminAndAccount : [TransferEventsV2.EventLog_HoldingsChange] -> [TransferEventsV2.EventLog_HoldingsChange] @@ -871,7 +912,7 @@ mergeHoldingChangesByAdminAndAccount events = mergeTwoHoldingChangeEvents e1 e2 = e1 with inputHoldingCids = e1.inputHoldingCids ++ e2.inputHoldingCids - transferLegs = e1.transferLegs ++ e2.transferLegs + transferLegSides = e1.transferLegSides ++ e2.transferLegSides outputHoldingCids = e1.outputHoldingCids ++ e2.outputHoldingCids observers = dedupSort (e1.observers ++ e2.observers) extraArgs = e1.extraArgs with diff --git a/token-standard/splice-token-standard-test-v2/daml/Splice/Tests/TestAccountProviderAllocation.daml b/token-standard/splice-token-standard-test-v2/daml/Splice/Tests/TestAccountProviderAllocation.daml index cd280c7e94..d61f0b68c7 100644 --- a/token-standard/splice-token-standard-test-v2/daml/Splice/Tests/TestAccountProviderAllocation.daml +++ b/token-standard/splice-token-standard-test-v2/daml/Splice/Tests/TestAccountProviderAllocation.daml @@ -13,6 +13,8 @@ import Splice.Api.Token.MetadataV1 import Splice.Api.Token.HoldingV2 qualified as V2 import Splice.Api.Token.AllocationV2 qualified as V2 import Splice.Api.Token.AllocationInstructionV2 qualified as V2 +import Splice.Api.Token.AllocationRequestV2 qualified as V2 +import Splice.TokenStandard.Utils as TSU import Splice.Testing.Utils @@ -178,7 +180,7 @@ test_allocation_flow env authorizer provider executors mkac initiator instr_acti sender = accountConfig.account receiver = voidAccount amount = 1.0 - instrumentId = instrX + instrumentId = instrX.id meta = emptyMetadata , V2.TransferLeg with @@ -186,20 +188,29 @@ test_allocation_flow env authorizer provider executors mkac initiator instr_acti sender = voidAccount receiver = accountConfig.account amount = 1.0 - instrumentId = instrY + instrumentId = instrY.id meta = emptyMetadata ] + transferLegSides = do + (authorizer, legSide) <- concatMap TSU.transferLegSidesWithAuthorizer transferLegs + guard (authorizer == accountConfig.account) + pure legSide settlement = V2.SettlementInfo with executors - requestedAt - settleAt settlementDeadline = None settlementRef = V2.Reference "foo" None meta = emptyMetadata request = TradingAppV2.OTCTradeAllocationRequest with authorizer = accountConfig.account + requestedAt + settleAt settlement - transferLegs + allocations = pure $ V2.RequestedAllocation with + admin = instrX.admin + transferLegSides + nextIterationFunding = None + committed = False + meta = emptyMetadata -- Create the allocation request submit executors do @@ -224,7 +235,7 @@ test_allocation_flow env authorizer provider executors mkac initiator instr_acti settle_allocation actors (allocCid, V2.AllocationView{..}) = do let batch = V2.SettlementFactory_SettleBatch with - allocationCids = [allocCid] + allocations = [nonIteratedAllocation allocCid] extraArgs = emptyExtraArgs .. @@ -239,6 +250,8 @@ test_allocation_flow env authorizer provider executors mkac initiator instr_acti exerciseCmd allocCid V2.Allocation_Settle with actors = actors' extraArgs = context.arg.extraArgs + nextIterationFunding = None + extraTransferLegSides = [] cancel_allocation actors (allocCid, _) = do registry <- MultiRegistry.getRegistryApiV2 registries env.testTokenV2.admin diff --git a/token-standard/splice-token-standard-test-v2/daml/Splice/Tests/TestAmuletTokenDvP_V1V2Mixed.daml b/token-standard/splice-token-standard-test-v2/daml/Splice/Tests/TestAmuletTokenDvP_V1V2Mixed.daml index 7584e2e200..36906ee9fb 100644 --- a/token-standard/splice-token-standard-test-v2/daml/Splice/Tests/TestAmuletTokenDvP_V1V2Mixed.daml +++ b/token-standard/splice-token-standard-test-v2/daml/Splice/Tests/TestAmuletTokenDvP_V1V2Mixed.daml @@ -390,20 +390,25 @@ runTest mkTest = do trader -- register OTC trade - let transferLegs = do + let tradeLegs = do (i, TestLeg{..}) <- zip [1..length test.transferLegs] test.transferLegs - pure V2.TransferLeg with - transferLegId = "leg" <> show i - sender = basicAccount sender - receiver = basicAccount receiver - amount - instrumentId = mkInstrumentId env instrumentId - meta = emptyMetadata + let iid = mkInstrumentId env instrumentId + let leg = V2.TransferLeg with + transferLegId = "leg" <> show i + sender = basicAccount sender + receiver = basicAccount receiver + amount + instrumentId = iid.id + meta = emptyMetadata + pure TradingAppV2.TradeLeg with + leg + admin = iid.admin + now <- getTime let settlementDeadline = now `addRelTime` hours 2 let otcTrade = TradingAppV2.OTCTrade with venue = env.venue - transferLegs + tradeLegs settleAt = now `addRelTime` hours 1 createdAt = now settlementDeadline = Some settlementDeadline @@ -467,7 +472,7 @@ runTest mkTest = do forA_ allocs $ \(allocCid, optAllocView) -> do -- TODO(#4517): also test lock expiry Some allocView <- pure optAllocView - registry <- MultiRegistry.getRegistryApiV2 env.registries (allocationAdmin allocView.allocation) + registry <- MultiRegistry.getRegistryApiV2 env.registries allocView.allocation.admin context <- RegistryApiV2.getAllocation_WithdrawContext registry allocCid emptyMetadata submitWithDisclosures' withdrawer context.disclosures $ exerciseCmd allocCid V2.Allocation_Withdraw with diff --git a/token-standard/splice-token-standard-test-v2/daml/Splice/Tests/TestIteratedSettlement.daml b/token-standard/splice-token-standard-test-v2/daml/Splice/Tests/TestIteratedSettlement.daml new file mode 100644 index 0000000000..05703deb71 --- /dev/null +++ b/token-standard/splice-token-standard-test-v2/daml/Splice/Tests/TestIteratedSettlement.daml @@ -0,0 +1,317 @@ +-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +-- | Daml script testing iterated settlement directly. +module Splice.Tests.TestIteratedSettlement where + +import Daml.Script + +import DA.Action +import DA.Assert +import DA.Foldable (forA_) +import DA.Map qualified as Map +import DA.Time +import DA.TextMap qualified as TextMap +import DA.Text qualified as T + +import Splice.Api.Token.MetadataV1 +import Splice.Api.Token.AllocationV2 qualified as V2 +import Splice.Api.Token.AllocationInstructionV2 qualified as V2 +import Splice.Api.Token.HoldingV2 qualified as V2 + +import Splice.TokenStandard.Utils + +import Splice.Testing.Utils +import Splice.Testing.Registries.AmuletRegistryV2 qualified as AmuletRegistryV2 +import Splice.Testing.Registries.TestRegistries qualified as TestRegistries +import Splice.Testing.TokenStandard.RegistryApiV2 qualified as V2 +import Splice.Testing.TokenStandard.WalletClientV2 qualified as WalletClientV2 + + +-- TODO(#5464): generalize the tests to also test TestTokenV2 + +-- | Test that an iterated allocation can be used to control locked funds. +-- The test uses a simplified billing workflow as an example. +test_locked_funds : Script () +test_locked_funds = do + env <- TestRegistries.initialize TestRegistries.defaultConfig + + provider <- allocateParty "provider" + alice <- allocateParty "alice" + + -- create some holdings for alice + let instrId = env.amuletV2.instrumentId + let registry = env.amuletV2 + let admins = Map.keys env.registries + AmuletRegistryV2.tapFaucet registry alice 1000.0 + + -- create prefunded allocation + let settlement = V2.SettlementInfo with + executors = [provider] + settlementRef = V2.Reference with + id = "billing/" + cid = None + settlementDeadline = None + meta = emptyMetadata + let alloc = V2.AllocationSpecification with + settlement + authorizer = basicAccount alice + admin = instrId.admin + transferLegSides = [] + nextIterationFunding = Some (TextMap.singleton instrId.id 100.0) + committed = True + meta = reasonToMeta "fund regular billing" emptyMetadata + now <- getTime + inputHoldingCids <- WalletClientV2.listHoldingCids alice instrId + enrichedChoice <- V2.getAllocationFactory registry V2.AllocationFactory_Allocate with + allocation = alloc + requestedAt = now + inputHoldingCids + actors = [alice] + extraArgs = emptyExtraArgs + + result <- WalletClientV2.submitAndCheckTxHistory admins (actAs alice <> discloseMany' enrichedChoice.disclosures) $ + exerciseCmd enrichedChoice.factoryCid enrichedChoice.arg + let V2.AllocationInstructionResult_Completed aliceAllocationCid = result.output + + WalletClientV2.checkUnlockedBalance alice instrId 900.0 + + -- check that allocation metadata is passed on + Some aliceAllocView <- queryInterfaceContractId alice aliceAllocationCid + aliceAllocView.allocation.meta === reasonToMeta "fund regular billing" emptyMetadata + + -- provider creates allocation to accept license fee payment + let billingLeg = V2.TransferLeg with + transferLegId = "billing-leg" + sender = basicAccount alice + receiver = basicAccount provider + amount = 0.5 + instrumentId = instrId.id + meta = reasonToMeta "daily license fee" emptyMetadata + enrichedChoice <- V2.getAllocationFactory registry V2.AllocationFactory_Allocate with + allocation = V2.AllocationSpecification with + settlement + authorizer = basicAccount provider + admin = instrId.admin + transferLegSides = [receiverSide billingLeg] + nextIterationFunding = None + committed = False + meta = emptyMetadata + requestedAt = now + inputHoldingCids = [] + actors = [provider] + extraArgs = emptyExtraArgs + result <- WalletClientV2.submitAndCheckTxHistory admins (actAs provider <> discloseMany' enrichedChoice.disclosures) $ + exerciseCmd enrichedChoice.factoryCid enrichedChoice.arg + let V2.AllocationInstructionResult_Completed providerAllocationCid = result.output + + -- provider settles billing against prefunded allocation + now <- getTime + enrichedChoice <- V2.getSettlementFactory registry V2.SettlementFactory_SettleBatch with + actors = [provider] + settlement + transferLegs = [billingLeg] + allocations = + [ nonIteratedAllocation providerAllocationCid + , V2.FinalizedAllocation with + allocationCid = aliceAllocationCid + extraTransferLegSides = [senderSide billingLeg] + nextIterationFunding = Some (TextMap.singleton instrId.id 99.0) + ] + extraArgs = emptyExtraArgs + + result <- WalletClientV2.submitAndCheckTxHistory admins (actAs provider <> discloseMany' enrichedChoice.disclosures) $ + exerciseCmd enrichedChoice.factoryCid enrichedChoice.arg + let [_, settleResult] = result.allocationSettleResults + let Some aliceAllocationCid = WalletClientV2.extractNextIterationAllocationCid settleResult + + -- payout of 0.5 Amulet to alice, and 0.5 Amulet to the provider + WalletClientV2.checkUnlockedBalance alice instrId 900.5 + WalletClientV2.checkUnlockedBalance provider instrId 0.5 + + -- test a top-up via a self-transfer + let topupLeg = V2.TransferLeg with + transferLegId = "topup-leg" + sender = basicAccount alice + receiver = basicAccount alice + amount = 200.0 + instrumentId = instrId.id + meta = reasonToMeta "topup funds for fee payments" emptyMetadata + inputHoldingCids <- WalletClientV2.listUnlockedHoldingCidsFor (basicAccount alice) alice instrId + enrichedChoice <- V2.getAllocationFactory registry V2.AllocationFactory_Allocate with + allocation = V2.AllocationSpecification with + settlement + authorizer = basicAccount alice + admin = instrId.admin + transferLegSides = [senderSide topupLeg] -- This is key: here we approve the sender-side, which locks the funds + nextIterationFunding = None + committed = False + meta = emptyMetadata + requestedAt = now + inputHoldingCids = inputHoldingCids + actors = [alice] + extraArgs = emptyExtraArgs + result <- WalletClientV2.submitAndCheckTxHistory admins (actAs alice <> discloseMany' enrichedChoice.disclosures) $ + exerciseCmd enrichedChoice.factoryCid enrichedChoice.arg + let V2.AllocationInstructionResult_Completed topupAllocationCid = result.output + + -- execute the top-up of alice's two allocations using batch settlement + enrichedChoice <- V2.getSettlementFactory registry V2.SettlementFactory_SettleBatch with + actors = [provider] + settlement + transferLegs = [topupLeg] + allocations = + [ nonIteratedAllocation topupAllocationCid + , V2.FinalizedAllocation with + allocationCid = aliceAllocationCid + extraTransferLegSides = [receiverSide topupLeg] + nextIterationFunding = Some (TextMap.singleton instrId.id 299.0) + ] + extraArgs = emptyExtraArgs + result <- WalletClientV2.submitAndCheckTxHistory admins (actAs provider <> discloseMany' enrichedChoice.disclosures) $ + exerciseCmd enrichedChoice.factoryCid enrichedChoice.arg + let [_, settleResult] = result.allocationSettleResults + let Some aliceAllocationCid = WalletClientV2.extractNextIterationAllocationCid settleResult + + -- there is exactly one allocation for alice + [(cid, Some aliceAllocView)] <- queryInterface @V2.Allocation alice + cid === aliceAllocationCid + aliceAllocView.allocation.nextIterationFunding === Some (TextMap.singleton instrId.id 299.0) + aliceAllocView.allocation.transferLegSides === [] + + -- there is one locked holding for alice + [(_, holding)] <- WalletClientV2.listLockedHoldings alice instrId + holding.amount === 299.0 + + -- the balances are as expected + WalletClientV2.checkBalance alice instrId 999.5 + WalletClientV2.checkBalance provider instrId 0.5 + + pure () + + +-- | Test that refresh on settlement and withdrawal of iterated allocations works as expected. +test_refresh_and_withdrawal : Script () +test_refresh_and_withdrawal = do + env <- TestRegistries.initialize TestRegistries.defaultConfig + + provider <- allocateParty "provider" + alice <- allocateParty "alice" + + -- fund alice + let instrId = env.amuletV2.instrumentId + let registry = env.amuletV2 + let dso = registry.dso + let admins = Map.keys env.registries + + -- create an allocation for iterated settlement with a settlement deadline in one year + now <- getTime + let aliceSettlementDeadline = addRelTime now (days 365) + let settlement = V2.SettlementInfo with + executors = [provider] + settlementRef = V2.Reference with + id = "billing/" + cid = None + settlementDeadline = Some aliceSettlementDeadline + meta = emptyMetadata + let alloc = V2.AllocationSpecification with + settlement + authorizer = basicAccount alice + admin = instrId.admin + transferLegSides = [] + nextIterationFunding = Some TextMap.empty + committed = True + meta = reasonToMeta "fund regular billing" emptyMetadata + inputHoldingCids <- WalletClientV2.listHoldingCids alice instrId + enrichedChoice <- V2.getAllocationFactory registry V2.AllocationFactory_Allocate with + allocation = alloc + requestedAt = now + inputHoldingCids + actors = [alice] + extraArgs = emptyExtraArgs + result <- WalletClientV2.submitAndCheckTxHistory admins (actAs alice <> discloseMany' enrichedChoice.disclosures) $ + exerciseCmd enrichedChoice.factoryCid enrichedChoice.arg + let V2.AllocationInstructionResult_Completed aliceAllocationCid0 = result.output + Some aliceAllocView0 <- queryInterfaceContractId alice aliceAllocationCid0 + checkAllocationsAndHoldingExpiryAgree dso + + -- withdrawing Alice's allocation must fail, as it is committed until its settlement deadline + context <- V2.getAllocation_WithdrawContext registry aliceAllocationCid0 emptyMetadata + result <- trySubmit (actAs alice <> discloseMany' context.disclosures) $ + exerciseCmd aliceAllocationCid0 V2.Allocation_Withdraw with + actors = [alice] + extraArgs = ExtraArgs with + context = context.choiceContext + meta = emptyMetadata + let (Left (FailureStatusError err)) = result + err === mkCannotWithdrawCommittedAllocationFailure (Some aliceSettlementDeadline) + + -- cycle allocation at different times + let refreshAllocation allocationCid = do + enrichedChoice <- V2.getSettlementFactory registry V2.SettlementFactory_SettleBatch with + actors = [provider] + settlement + transferLegs = [] + allocations = + [ V2.FinalizedAllocation with + allocationCid + extraTransferLegSides = [] + nextIterationFunding = Some TextMap.empty + ] + extraArgs = emptyExtraArgs + + result <- WalletClientV2.submitAndCheckTxHistory admins (actAs provider <> discloseMany' enrichedChoice.disclosures) $ + exerciseCmd enrichedChoice.factoryCid enrichedChoice.arg + let [settleResult] = result.allocationSettleResults + let Some aliceAllocationCid = WalletClientV2.extractNextIterationAllocationCid settleResult + Some aliceAllocView <- queryInterfaceContractId alice aliceAllocationCid + pure (aliceAllocationCid, aliceAllocView) + + -- immediate refresh should work and not bump expiry + (aliceAllocationCid1, aliceAllocView1) <- refreshAllocation aliceAllocationCid0 + aliceAllocView0.expiresAt === aliceAllocView1.expiresAt + checkAllocationsAndHoldingExpiryAgree dso + + -- refresh after 45 days still does not bump expiry + passTime (days 45) + (aliceAllocationCid1, aliceAllocView1) <- refreshAllocation aliceAllocationCid1 + aliceAllocView0.expiresAt === aliceAllocView1.expiresAt + + -- refresh after 45 days and 1 microsecond should bump expiry + passTime (microseconds 1) + (aliceAllocationCid1, aliceAllocView1) <- refreshAllocation aliceAllocationCid1 + (fmap (`addRelTime` days 45) aliceAllocView0.expiresAt) === aliceAllocView1.expiresAt + checkAllocationsAndHoldingExpiryAgree dso + + -- withdrawing Alice's allocation after the settlement deadline must succeed + setTime (aliceSettlementDeadline `addRelTime` microseconds 1) + context <- V2.getAllocation_WithdrawContext registry aliceAllocationCid1 emptyMetadata + WalletClientV2.submitAndCheckTxHistory admins (actAs alice <> discloseMany' context.disclosures) $ + exerciseCmd aliceAllocationCid1 V2.Allocation_Withdraw with + actors = [alice] + extraArgs = ExtraArgs with + context = context.choiceContext + meta = emptyMetadata + + [] <- queryInterface @V2.Allocation alice + + pure () + + + +checkAllocationsAndHoldingExpiryAgree : Party -> Script () +checkAllocationsAndHoldingExpiryAgree admin = do + allocs <- queryInterface @V2.Allocation admin + forA_ allocs $ \(_, Some allocView) -> do + let allocationExpiry = allocView.expiresAt + forA_ allocView.holdingCids $ \holdingCid -> do + Some holdingView <- queryInterfaceContractId @V2.Holding admin holdingCid + let lockExpiry = fmap (.expiresAt) holdingView.lock + unless (lockExpiry == Some allocationExpiry) $ + fail $ T.unlines + [ "Expected allocation and holding expiry to agree, but they differ:" + , " allocation: " <> show allocView + , " holding: " <> show holdingView + , " allocation expiry: " <> show allocationExpiry + , " holding lock expiry: " <> show lockExpiry + ] diff --git a/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils.daml b/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils.daml index 76fa3637c1..bd7568b748 100644 --- a/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils.daml +++ b/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils.daml @@ -53,6 +53,10 @@ module Splice.TokenStandard.Utils ( reasonToMeta, mkMetaTransferFrom, + -- * Missing Time utilities + -- TODO(#4865): upstream these to the Daml stdlib + maxTime, + -- * Missing TextMap utilities -- TODO(#4865): upstream these to the Daml stdlib @@ -74,8 +78,6 @@ module Splice.TokenStandard.Utils ( burnAccount, mintAccount, - allocationToTransferEventLeg, - logMint, logBurn, logNonEmptyChange, @@ -83,14 +85,20 @@ module Splice.TokenStandard.Utils ( logTransfer, logAllocationSettlement, + -- * Allocation workflows -- ** Utilities for allocations - allocationAdmin, + netAllocationCreditAmounts, - filterLegsForAccount, - splitLegsByAdmin, - splitLegsByAuthorizer, + requestedAllocationToSpecification, + nonIteratedAllocation, + + -- *** Working with transfer legs and their sides + senderSide, + receiverSide, + transferLegFromSide, + transferLegSidesWithAuthorizer, -- *** Auto-creation of receipt allocations ensureIsReceiptAllocation, @@ -110,12 +118,17 @@ module Splice.TokenStandard.Utils ( -- ** Non-standard conversions upcast_v1_v2_TransferLeg, + downcast_v2_v1_TransferLeg, + downcast_v2_v1_SettlementInfo, upcast_v1_v2_Allocation, -- *** Choices allocationV2_withdrawDefaultImplUsingV1, allocationV2_cancelDefaultImplUsingV1, + ensureWithdrawIsAllowed, + mkCannotWithdrawCommittedAllocationFailure, + -- ** Settlement factory implementation settlementFactoryV2_settleBatchDefaultImpl, diff --git a/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils/Internal/Allocations.daml b/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils/Internal/Allocations.daml index c9e46bce18..ff813e1d80 100644 --- a/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils/Internal/Allocations.daml +++ b/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils/Internal/Allocations.daml @@ -8,9 +8,14 @@ module Splice.TokenStandard.Utils.Internal.Allocations ( -- * Allocation utilities netAllocationCreditAmounts, - splitLegsByAdmin, - splitLegsByAuthorizer, - filterLegsForAccount, + requestedAllocationToSpecification, + nonIteratedAllocation, + + -- ** Working with transfer legs and their sides + senderSide, + receiverSide, + transferLegFromSide, + transferLegSidesWithAuthorizer, -- ** Auto-creation of receipt allocations ensureIsReceiptAllocation, @@ -30,12 +35,18 @@ module Splice.TokenStandard.Utils.Internal.Allocations ( -- ** Non-standard conversions upcast_v1_v2_TransferLeg, + downcast_v2_v1_TransferLeg, + + downcast_v2_v1_SettlementInfo, upcast_v1_v2_Allocation, -- ** Choice implementations allocationV2_withdrawDefaultImplUsingV1, allocationV2_cancelDefaultImplUsingV1, + ensureWithdrawIsAllowed, + mkCannotWithdrawCommittedAllocationFailure, + -- * SettlementFactory template implementations settlementFactoryV2_settleBatchDefaultImpl, fetchAndValidateAllocations, @@ -72,14 +83,18 @@ module Splice.TokenStandard.Utils.Internal.Allocations ( ) where +import DA.Action +import DA.Fail (failWithStatus, FailureStatus(..), FailureCategory(..)) import DA.Foldable (forA_) import DA.List (dedupSort, unique) import qualified DA.Map as Map -import DA.Optional (fromOptional) +import DA.Optional import qualified DA.Set as Set import DA.TextMap qualified as TextMap +import DA.Time (isLedgerTimeGT) import Splice.Api.Token.MetadataV1 +import Splice.Api.Token.HoldingV1 qualified as HoldingV1 import Splice.Api.Token.HoldingV2 qualified as HoldingV2 import Splice.Api.Token.AllocationV1 qualified as AllocationV1 import Splice.Api.Token.AllocationV2 qualified as AllocationV2 @@ -95,34 +110,49 @@ import Splice.Api.Token.AllocationV2 (AllocationResult(AllocationResult)) -- Allocation utilities ------------------------ --- | Filter transfer legs to the ones affecting the given account. -filterLegsForAccount : HoldingV2.Account -> [AllocationV2.TransferLeg] -> [AllocationV2.TransferLeg] -filterLegsForAccount account transferLegs = - filter (\tl -> account == tl.sender || account == tl.receiver) transferLegs +-- | The sender side of a transfer leg. +senderSide : AllocationV2.TransferLeg -> AllocationV2.TransferLegSide +senderSide (AllocationV2.TransferLeg with ..) = + AllocationV2.TransferLegSide with side = AllocationV2.SenderSide; otherside = receiver; .. --- | Group transfer legs by the account that needs to authorize them. -splitLegsByAuthorizer : [AllocationV2.TransferLeg] -> Map.Map HoldingV2.Account [AllocationV2.TransferLeg] -splitLegsByAuthorizer legs = Map.fromListWithR (++) $ do - leg <- legs - if leg.sender == leg.receiver - then [(leg.sender, [leg])] -- self-transfers are authorized only once - else [(leg.sender, [leg]), (leg.receiver, [leg])] +-- | The receiver side of a transfer leg. +receiverSide : AllocationV2.TransferLeg -> AllocationV2.TransferLegSide +receiverSide (AllocationV2.TransferLeg with ..) = + AllocationV2.TransferLegSide with side = AllocationV2.ReceiverSide; otherside = sender; .. --- | Group transfer legs by the admin of their instrument. --- --- Use this to group them into batches for settlement, which is done on a per-admin basis. -splitLegsByAdmin : [AllocationV2.TransferLeg] -> Map.Map Party [AllocationV2.TransferLeg] -splitLegsByAdmin legs = Map.fromListWithR (++) $ map (\leg -> (leg.instrumentId.admin, [leg])) legs - --- | Net amounts of credit from an allocation's transfer legs. -netAllocationCreditAmounts : HoldingV2.Account -> [AllocationV2.TransferLeg] -> Map.Map HoldingV2.InstrumentId Decimal -netAllocationCreditAmounts authorizer transferLegs = - Map.fromListWithR (+) $ map legAmount transferLegs +-- | Create a transfer-leg from a side and its authorizer. +transferLegFromSide : HoldingV2.Account -> AllocationV2.TransferLegSide -> AllocationV2.TransferLeg +transferLegFromSide authorizer (AllocationV2.TransferLegSide with ..) = + AllocationV2.TransferLeg with .. + where + (sender, receiver) = case side of + AllocationV2.SenderSide -> (authorizer, otherside) + AllocationV2.ReceiverSide -> (otherside, authorizer) + +-- | Smart-constructor to finalize a non-iterated allocation for settlement. +nonIteratedAllocation : ContractId AllocationV2.Allocation -> AllocationV2.FinalizedAllocation +nonIteratedAllocation allocationCid = AllocationV2.FinalizedAllocation with + allocationCid + extraTransferLegSides = [] + nextIterationFunding = None + +-- | Convert a requested allocation to a specification. +requestedAllocationToSpecification + : AllocationV2.SettlementInfo -> HoldingV2.Account -> AllocationRequestV2.RequestedAllocation + -> AllocationV2.AllocationSpecification +requestedAllocationToSpecification settlement authorizer (AllocationRequestV2.RequestedAllocation with ..) = + AllocationV2.AllocationSpecification with .. + +-- | Net amounts of credit from an allocation's transfer leg sides. +netAllocationCreditAmounts : HoldingV2.Account -> [AllocationV2.TransferLegSide] -> TextMap.TextMap Decimal +netAllocationCreditAmounts authorizer transferLegSides = + TextMap.fromListWithR (+) $ map legAmount transferLegSides where legAmount leg = ( leg.instrumentId - , (if leg.sender == authorizer then negate leg.amount else 0.0) + - (if leg.receiver == authorizer then leg.amount else 0.0) + , case leg.side of + AllocationV2.SenderSide -> negate leg.amount + AllocationV2.ReceiverSide -> leg.amount ) -- | Check basic invariants of a `V1.SettlementInfo`. @@ -142,27 +172,22 @@ isValidAllocationSpecificationV1 AllocationV1.AllocationSpecification{..} = -- | Check basic invariants of a `V2.SettlementInfo`. isValidSettlementInfoV2 : AllocationV2.SettlementInfo -> Bool isValidSettlementInfoV2 AllocationV2.SettlementInfo{..} = - not (null executors) && - requestedAt <= settleAt && - optional True (settleAt <=) settlementDeadline && - validateNoMeta extraExecutorsMetaField meta + not (null executors) && validateNoMeta extraExecutorsMetaField meta -- | Check basic invariants of a `V2.AllocationSpecification`. isValidAllocationSpecificationV2 - : (HoldingV2.InstrumentId -> Bool) - -- ^ Validation for the instrument ID, SHOULD check equality on the `admin`. + : (Text -> Bool) + -- ^ Validation for the instrument ID. -> AllocationV2.AllocationSpecification -> Bool isValidAllocationSpecificationV2 validInstrumentId AllocationV2.AllocationSpecification{..} = isValidSettlementInfoV2 settlement && - not (null transferLegs) && - unique transferLegs && - all isValidLeg transferLegs + (not (null transferLegSides) || isSome nextIterationFunding) && -- empty transfer legs are only allowed for iterated settlement + unique (map (\leg -> (leg.transferLegId, leg.side)) transferLegSides) && + all isValidLeg transferLegSides && + all validInstrumentId (optional [] textMapKeys nextIterationFunding) where isValidLeg leg = - -- Note: we allow self-transfers as the metadata might actually give them additional meaning leg.amount > 0.0 && - -- Authorizer needs to appear as sender or receiver of the leg - authorizer `elem` [leg.sender, leg.receiver] && validInstrumentId leg.instrumentId && -- No account meta allowed on the leg for sender or receiver to avoid confusion validateNoAccountMeta "sender" leg.meta && @@ -182,18 +207,14 @@ ensureIsReceiptAllocation executors receiver receiptAccount choiceArg = do let (AllocationInstructionV2.AllocationFactory_Allocate with allocation; requestedAt = _ignore; inputHoldingCids; extraArgs; actors) = choiceArg let (ExtraArgs with context = _ignore; meta) = extraArgs - let (AllocationV2.AllocationSpecification with settlement; transferLegs; authorizer) = allocation + let (AllocationV2.AllocationSpecification with settlement; transferLegSides; authorizer) = allocation requireMatchExpected ("allocation.settlement.executor", settlement.executors) executors require' ("choiceArg.allocation.authorizer", authorizer) isEqualR ("receiptAccount", receiptAccount) -- restrict to single actor choices with the receiver as the actor requireMatchExpected ("choiceArg.actors", actors) [receiver] -- require all transfer legs to be receipts - forA_ transferLegs $ \leg -> do - let legName = "transferLeg[" <> leg.transferLegId <> "]" - require' (legName <> ".receiver", leg.receiver) isEqualR ("receiptAccount", receiptAccount) - -- avoid accidental delegation of authority for non-receipt legs - require' (legName <> ".sender.owner", leg.sender.owner) (notR isEqualR) ("receivingActor", receiver) - require' (legName <> ".sender.provider", leg.sender.provider) (notR isEqualR) ("receivingActor", Some receiver) + forA_ transferLegSides $ \leg -> do + require ("transferLeg[" <> leg.transferLegId <> "] is a receipt leg") (leg.side == AllocationV2.ReceiverSide) -- no holdings for funding require' ("choiceArg.inputHoldingCids", inputHoldingCids) isEqualR ("emptyList", []) -- no extra metadata to avoid accidental delegation over special functionality @@ -203,6 +224,32 @@ ensureIsReceiptAllocation executors receiver receiptAccount choiceArg = do -- Allocation choices --------------------- +-- | Report a failure when trying to withdraw a committed allocation. +mkCannotWithdrawCommittedAllocationFailure : Optional Time -> FailureStatus +mkCannotWithdrawCommittedAllocationFailure None = FailureStatus with + errorId = "splice.lfdecentralizedtrust.org/cannot-withdraw-committed-allocation" + message = "Cannot withdraw a committed allocation that has no settlement deadline." + category = InvalidIndependentOfSystemState + meta = TextMap.empty +mkCannotWithdrawCommittedAllocationFailure (Some settlementDeadline) = FailureStatus with + errorId = "splice.lfdecentralizedtrust.org/cannot-withdraw-committed-allocation" + message = "Cannot withdraw a committed allocation before its settlement deadline." + category = InvalidGivenCurrentSystemStateOther + meta = TextMap.fromList [("settlementDeadline", show settlementDeadline)] + +-- | Ensure that withdraw is allowed given the allocation's committment status +-- and settlement deadline. +ensureWithdrawIsAllowed : AllocationV2.AllocationSpecification -> Update () +ensureWithdrawIsAllowed allocation = + when allocation.committed $ do + case allocation.settlement.settlementDeadline of + None -> failWithStatus (mkCannotWithdrawCommittedAllocationFailure None) + Some deadline -> do + isPastDeadline <- isLedgerTimeGT deadline + unless isPastDeadline $ + failWithStatus (mkCannotWithdrawCommittedAllocationFailure (Some deadline)) + + -- | Default implementation for the `V2.Allocation_Withdraw` for a template that -- implements the `V1.Allocation` interface. -- @@ -294,16 +341,17 @@ settlementFactoryV2_settleBatchDefaultImpl admin self arg = do allocations <- fetchAndValidateAllocations admin arg -- settle all allocations - newHoldingCidss <- forA allocations $ \(allocationCid, allocView) -> do - result <- exercise allocationCid AllocationV2.Allocation_Settle with + allocationSettleResults <- forA allocations $ \(finalizedAlloc, allocView) -> do + exercise finalizedAlloc.allocationCid AllocationV2.Allocation_Settle with -- Use default controllers as the actors - actors = AllocationV2.defaultAllocation_SettleControllers allocView + actors = allocView.allocation.admin :: allocView.allocation.settlement.executors + extraTransferLegSides = finalizedAlloc.extraTransferLegSides + nextIterationFunding = finalizedAlloc.nextIterationFunding extraArgs - pure (allocView.allocation.authorizer, result.authorizerHoldingCids) -- return result pure AllocationV2.SettlementFactory_SettleBatchResult with - newHoldingCids = Map.fromListWithR (textMapUnionWith (++)) newHoldingCidss + allocationSettleResults meta = emptyMetadata -- | Core logic to validate the allocations for a batch settlement. @@ -311,30 +359,40 @@ settlementFactoryV2_settleBatchDefaultImpl admin self arg = do -- be reused by factories that need custom settlement execution logic. fetchAndValidateAllocations : Party -> AllocationV2.SettlementFactory_SettleBatch - -> Update [(ContractId AllocationV2.Allocation, AllocationV2.AllocationView)] + -> Update [(AllocationV2.FinalizedAllocation, AllocationV2.AllocationView)] fetchAndValidateAllocations admin AllocationV2.SettlementFactory_SettleBatch{..} = do - requireUnique "SettleBatch.transferLegs ids" (map (.transferLegId) transferLegs) + requireUnique "SettleBatch.transferLegsIds" (map (.transferLegId) transferLegs) -- fetch and validate all allocations - allocations <- forA allocationCids $ \allocationCid -> do - allocation0 <- fetch allocationCid + allocations <- forA allocations $ \finalizedAlloc -> do + allocation0 <- fetch finalizedAlloc.allocationCid let allocationView = view allocation0 let allocation = allocationView.allocation requireMatchExpected ("allocation.settlement", allocation.settlement) settlement - forA_ allocation.transferLegs $ \leg -> do - requireMatchExpected ("transferLeg.instrumentId.admin", leg.instrumentId.admin) admin - pure (allocationCid, allocationView) + requireMatchExpected ("allocation.admin", allocation.admin) admin + -- prudent engineering: check that iterated settlement is allowed, when it is used + unless (isSome (allocation.nextIterationFunding)) $ do + requireMatchExpected ("finalizedAllocation.extraTransferLegs", finalizedAlloc.extraTransferLegSides) [] + requireMatchExpected ("finalizedAllocation.nextIterationFunding", finalizedAlloc.nextIterationFunding) None + forA_ finalizedAlloc.nextIterationFunding $ \fundings -> forA_ fundings $ \amount -> do + require' ("finalizedAllocation.nextIterationFunding.amount", amount) isGreaterR ("zero", 0.0) + + let adjustedAllocView = allocationView with + allocation = allocation with + transferLegSides = allocation.transferLegSides ++ finalizedAlloc.extraTransferLegSides + + pure (finalizedAlloc, adjustedAllocView) -- ensure there are no duplicate authorizations let allocatedAuthorizations = do (_, allocationView) <- allocations - leg <- allocationView.allocation.transferLegs + leg <- allocationView.allocation.transferLegSides pure (allocationView.allocation.authorizer, leg) requireUnique "allocated authorizations" allocatedAuthorizations -- check that exactly the required allocations are present - let requiredAuthorizations = Set.fromList $ concatMap toAuthorizedLegs transferLegs + let requiredAuthorizations = Set.fromList $ concatMap transferLegSidesWithAuthorizer transferLegs let allocatedAuthorizationsSet = Set.fromList allocatedAuthorizations let superfluousAuthorizations = allocatedAuthorizationsSet `Set.difference` requiredAuthorizations let missingAuthorizations = requiredAuthorizations `Set.difference` allocatedAuthorizationsSet @@ -345,13 +403,10 @@ fetchAndValidateAllocations admin AllocationV2.SettlementFactory_SettleBatch{..} pure allocations --- | Helper type of a transfer leg together with its authorizer. -type AuthorizedLeg = (HoldingV2.Account, AllocationV2.TransferLeg) +-- | Compute a transfer leg's sides and their authorizers. +transferLegSidesWithAuthorizer : AllocationV2.TransferLeg -> [(HoldingV2.Account, AllocationV2.TransferLegSide)] +transferLegSidesWithAuthorizer leg = [(leg.sender, senderSide leg), (leg.receiver, receiverSide leg)] -toAuthorizedLegs : AllocationV2.TransferLeg -> [AuthorizedLeg] -toAuthorizedLegs leg - | leg.sender == leg.receiver = [(leg.sender, leg)] -- self-transfers are authorized only once - | otherwise = [(leg.sender, leg), (leg.receiver, leg)] -- Allocation casts @@ -367,13 +422,13 @@ instance ForwardCompatible AllocationV1.Reference AllocationV2.Reference where instance BackwardCompatible AllocationV1.Reference AllocationV2.Reference where upcast AllocationV1.Reference{..} = AllocationV2.Reference with .. -instance ForwardCompatible AllocationV1.TransferLeg AllocationV2.TransferLeg where - downcast (AllocationV2.TransferLeg with sender, receiver, amount, instrumentId, meta) = +downcast_v2_v1_TransferLeg : Party -> AllocationV2.TransferLeg -> AllocationV1.TransferLeg +downcast_v2_v1_TransferLeg admin (AllocationV2.TransferLeg with sender, receiver, amount, instrumentId, meta) = AllocationV1.TransferLeg with sender = sender.owner receiver = receiver.owner amount - instrumentId = downcast instrumentId + instrumentId = HoldingV1.InstrumentId with admin; id = instrumentId meta = accountToMeta "sender" sender $ accountToMeta "receiver" receiver $ @@ -381,42 +436,45 @@ instance ForwardCompatible AllocationV1.TransferLeg AllocationV2.TransferLeg whe upcast_v1_v2_TransferLeg : Text -> AllocationV1.TransferLeg -> AllocationV2.TransferLeg upcast_v1_v2_TransferLeg transferLegId (AllocationV1.TransferLeg with sender, receiver, amount, instrumentId, meta) = - AllocationV2.TransferLeg with - transferLegId - sender = accountFromMeta sender "sender" meta - receiver = accountFromMeta receiver "receiver" meta - amount - instrumentId = upcast instrumentId - meta = dropAccountMeta "sender" $ dropAccountMeta "receiver" meta + AllocationV2.TransferLeg with + transferLegId + sender = accountFromMeta sender "sender" meta + receiver = accountFromMeta receiver "receiver" meta + amount + instrumentId = instrumentId.id + meta = dropAccountMeta "sender" $ dropAccountMeta "receiver" meta instance BackwardCompatible AllocationV1.SettlementInfo AllocationV2.SettlementInfo where upcast (AllocationV1.SettlementInfo with executor, settlementRef, requestedAt, allocateBefore, settleBefore, meta) = AllocationV2.SettlementInfo with executors = executor :: partiesFromMeta extraExecutorsMetaField meta settlementRef = upcast settlementRef - requestedAt - settleAt = allocateBefore settlementDeadline = if settleBefore == maxTime then None else Some settleBefore meta = dropMeta extraExecutorsMetaField meta -instance ForwardCompatible AllocationV1.SettlementInfo AllocationV2.SettlementInfo where - downcast (AllocationV2.SettlementInfo with executors, settlementRef, requestedAt, settleAt, settlementDeadline, meta) = - AllocationV1.SettlementInfo with - executor - settlementRef = downcast settlementRef - requestedAt - allocateBefore = settleAt - settleBefore = fromOptional maxTime settlementDeadline - meta = partiesToMeta extraExecutorsMetaField extraExecutors meta - where - executor :: extraExecutors = executors +downcast_v2_v1_SettlementInfo : Time -> Time -> AllocationV2.SettlementInfo -> AllocationV1.SettlementInfo +downcast_v2_v1_SettlementInfo requestedAt settleAt (AllocationV2.SettlementInfo with executors, settlementRef, settlementDeadline, meta) = + AllocationV1.SettlementInfo with + executor + settlementRef = downcast settlementRef + requestedAt + allocateBefore = settleAt + settleBefore = fromOptional maxTime settlementDeadline + meta = partiesToMeta extraExecutorsMetaField extraExecutors meta + where + executor :: extraExecutors = executors instance BackwardCompatible AllocationV1.AllocationSpecification AllocationV2.AllocationSpecification where upcast (AllocationV1.AllocationSpecification with settlement, transferLegId, transferLeg) = AllocationV2.AllocationSpecification with + admin = transferLeg.instrumentId.admin authorizer = accountFromMeta transferLeg.sender "sender" transferLeg.meta settlement = upcast settlement - transferLegs = [upcast_v1_v2_TransferLeg transferLegId transferLeg] + -- V1 allocations only approve the sending side of the transfer leg; as receipt authorization is implicit in V1 + transferLegSides = [senderSide $ upcast_v1_v2_TransferLeg transferLegId transferLeg] + nextIterationFunding = None + committed = False + meta = emptyMetadata -- | Use this instance only if your allocation does not evolve over time, as it -- always uses `None` for the `originalAllocationCid`. @@ -434,6 +492,8 @@ upcast_v1_v2_Allocation originalAllocationCid availableActions (AllocationV1.All holdingCids = map upcast holdingCids expiresAt = Some (allocation.settlement.settleBefore) availableActions + createdAt = allocation.settlement.requestedAt + numIterations = 0 meta -- | Available actions for a V1 allocation that uses the default controllers for its actions. @@ -451,12 +511,8 @@ allocationV2_availableActionsDefault : AllocationV2.AllocationSpecification -> M allocationV2_availableActionsDefault allocation = Map.fromList [ ([allocation.authorizer.owner], [AllocationV2.AA_Withdraw]) , (dedupSort allocation.settlement.executors, [AllocationV2.AA_Cancel]) - , (dedupSort (admin :: allocation.settlement.executors), [AllocationV2.AA_Settle]) + , (dedupSort (allocation.admin :: allocation.settlement.executors), [AllocationV2.AA_Settle]) ] - where - admin = case allocation.transferLegs of - leg :: _ -> leg.instrumentId.admin - [] -> error "unexpected: allocation with no transfer legs" instance BackwardCompatible (ContractId AllocationV1.Allocation) (ContractId AllocationV2.Allocation) where upcast = coerceInterfaceContractId @@ -618,6 +674,7 @@ allocationRequestV2_withdrawDefaultImplV2Only this self arg = do archiveAndCheckActors self arg.actors [(view this).settlement.executors] pure AllocationRequestV2.AllocationRequest_WithdrawResult with meta = emptyMetadata + -- | Note we are not offering the upcast from V1 to V2 for the allocation request views, -- as the authorizer on V2 is lost this way. The downcast is still useful, as it -- simplifies implementing dual version V1/V2 allocation requests. @@ -625,11 +682,19 @@ allocationRequestV2_withdrawDefaultImplV2Only this self arg = do -- V1 wallets will create one V1 allocation per transfer leg with their wallet party as the -- sender. Their authorizer will thus be set to the `sender.owner` of each individual transfer leg. instance ForwardCompatible AllocationRequestV1.AllocationRequestView AllocationRequestV2.AllocationRequestView where - downcast (AllocationRequestV2.AllocationRequestView with settlement, transferLegs, meta) = - AllocationRequestV1.AllocationRequestView with - settlement = downcast settlement - transferLegs = TextMap.fromList [ (leg.transferLegId, downcast leg) | leg <- transferLegs ] - meta + downcast (AllocationRequestV2.AllocationRequestView with settlement, requestedAt, authorizer, settleAt, allocations, meta) = + AllocationRequestV1.AllocationRequestView with + settlement = downcast_v2_v1_SettlementInfo requestedAt (fromOptional maxTime settleAt) settlement + transferLegs = TextMap.fromList $ do + alloc <- allocations + leg <- alloc.transferLegSides + -- Note: self-transfers will be present twice, but building the map + -- based on the transfer leg id will take care of deduplicating them. + let legV2 = transferLegFromSide authorizer leg + pure (leg.transferLegId, downcast_v2_v1_TransferLeg alloc.admin legV2) + meta + where + executor :: extraExecutors = settlement.executors -- Allocation factories @@ -647,7 +712,9 @@ allocationFactoryV2_publicAsset_allocateExtraObserversDefaultImpl arg = allocationFactoryV2_privateAsset_allocateExtraObserversDefaultImpl : AllocationInstructionV2.AllocationFactory_Allocate -> [Party] allocationFactoryV2_privateAsset_allocateExtraObserversDefaultImpl arg = accountParties allocation.authorizer <> - (if all (>= 0.0) (Map.values creditAmounts) then arg.allocation.settlement.executors else []) + (if all (>= 0.0) (textMapValues creditAmounts) then arg.allocation.settlement.executors else []) where allocation = arg.allocation - creditAmounts = netAllocationCreditAmounts allocation.authorizer allocation.transferLegs + creditAmounts = netAllocationCreditAmounts allocation.authorizer allocation.transferLegSides + + diff --git a/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils/Internal/Conversions.daml b/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils/Internal/Conversions.daml index 09c16efcaa..8609526f6b 100644 --- a/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils/Internal/Conversions.daml +++ b/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils/Internal/Conversions.daml @@ -22,7 +22,7 @@ module Splice.TokenStandard.Utils.Internal.Conversions ( dropMeta, validateNoMeta, - -- * Transfer metadata + -- * Transfer utils reasonMetaKey, txKindMetaKey, senderMetaKey, @@ -47,9 +47,6 @@ module Splice.TokenStandard.Utils.Internal.Conversions ( -- ** Internal metadata functions tokenStandardV2Namespace, - -- * Pure allocation utilities - allocationAdmin, - ) where import DA.Action @@ -62,7 +59,6 @@ import DA.TextMap qualified as TextMap import Splice.Api.Token.MetadataV1 import Splice.Api.Token.HoldingV1 qualified as HoldingV1 import Splice.Api.Token.HoldingV2 qualified as HoldingV2 -import Splice.Api.Token.AllocationV2 qualified as AllocationV2 @@ -82,13 +78,6 @@ decodeTime t = case T.parseInt t of Some i -> epoch `addRelTime` (convertMicrosecondsToRelTime i) None -> error ("Cannot parse time " <> t) --- Note: placed here to avoid a circular dependency -allocationAdmin : AllocationV2.AllocationSpecification -> Party -allocationAdmin AllocationV2.AllocationSpecification{..} = - case transferLegs of - [] -> error "No transfer legs in allocation" - (leg ::_) -> leg.instrumentId.admin - -- AnyContractId conversion ------------------------------ diff --git a/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils/Internal/Events.daml b/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils/Internal/Events.daml index 94d94bbcac..7b2fa9be34 100644 --- a/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils/Internal/Events.daml +++ b/token-standard/splice-token-standard-utils/daml/Splice/TokenStandard/Utils/Internal/Events.daml @@ -14,7 +14,7 @@ module Splice.TokenStandard.Utils.Internal.Events ( mintAccount, -- * Utilities to log events - allocationToTransferEventLeg, + allocationToTransferEventLegSide, logMint, logBurn, @@ -67,35 +67,40 @@ burnAccount p = V2.Account with provider = None id = burnAccountId -allocationToTransferEventLeg : AllocationV2.TransferLeg -> TransferEventsV2.TransferLeg -allocationToTransferEventLeg (AllocationV2.TransferLeg with transferLegId, sender, receiver, amount, instrumentId, meta) = - TransferEventsV2.TransferLeg with transferLegId, sender, receiver, amount, instrumentId, meta +allocationToTransferEventSide : AllocationV2.TransferSide -> TransferEventsV2.TransferSide +allocationToTransferEventSide side = case side of + AllocationV2.SenderSide -> TransferEventsV2.SenderSide + AllocationV2.ReceiverSide -> TransferEventsV2.ReceiverSide + +allocationToTransferEventLegSide : AllocationV2.TransferLegSide -> TransferEventsV2.TransferLegSide +allocationToTransferEventLegSide (AllocationV2.TransferLegSide with side; ..) = + TransferEventsV2.TransferLegSide with side = allocationToTransferEventSide side; .. -- | Convenience function to avoid logging change events that report neither a holdings change nor a transfer leg. logNonEmptyChange : ContractId TransferEventsV2.EventLog -> TransferEventsV2.EventLog_HoldingsChange -> Update () logNonEmptyChange eventLogCid arg = do - unless (null arg.transferLegs && null arg.inputHoldingCids && null arg.outputHoldingCids) $ + unless (null arg.transferLegSides && null arg.inputHoldingCids && null arg.outputHoldingCids) $ void $ exercise eventLogCid arg logMint : ContractId TransferEventsV2.EventLog -> V2.Account -> Decimal -> V2.InstrumentId -> Metadata -> [ContractId V2.Holding] -> Update TransferEventsV2.EventLog_HoldingsChangeResult logMint factoryCid receiver amount instrumentId meta outputHoldingCids = do - let leg = TransferEventsV2.TransferLeg with + let leg = TransferEventsV2.TransferLegSide with transferLegId = "mint" - sender = V2.Account with + side = TransferEventsV2.ReceiverSide + otherside = V2.Account with owner = instrumentId.admin provider = None id = mintAccountId - receiver - instrumentId + instrumentId = instrumentId.id amount meta exercise factoryCid TransferEventsV2.EventLog_HoldingsChange with admin = instrumentId.admin account = receiver inputHoldingCids = [] - transferLegs = [leg] + transferLegSides = [leg] outputHoldingCids observers = accountParties receiver extraArgs = emptyExtraArgs @@ -104,21 +109,21 @@ logBurn : ContractId TransferEventsV2.EventLog -> V2.Account -> Decimal -> V2.InstrumentId -> Metadata -> [ContractId V2.Holding] -> Update TransferEventsV2.EventLog_HoldingsChangeResult logBurn factoryCid sender amount instrumentId meta inputHoldingCids = do - let leg = TransferEventsV2.TransferLeg with + let leg = TransferEventsV2.TransferLegSide with transferLegId = "burn" - sender - receiver = V2.Account with + side = TransferEventsV2.SenderSide + otherside = V2.Account with owner = instrumentId.admin provider = None id = burnAccountId - instrumentId + instrumentId = instrumentId.id amount meta exercise factoryCid TransferEventsV2.EventLog_HoldingsChange with admin = instrumentId.admin account = sender inputHoldingCids - transferLegs = [leg] + transferLegSides = [leg] outputHoldingCids = [] observers = accountParties sender extraArgs = emptyExtraArgs @@ -131,13 +136,12 @@ logAllocationSettlement -> [ContractId V2.Holding] -- ^ Output holdings -> Update () logAllocationSettlement eventLogCid inputHoldingCids allocation outputHoldingCids = do - let admin = allocationAdmin allocation let settlementId = allocation.settlement.settlementRef.id - void $ exercise eventLogCid TransferEventsV2.EventLog_HoldingsChange with - admin + void $ logNonEmptyChange eventLogCid TransferEventsV2.EventLog_HoldingsChange with + admin = allocation.admin account = allocation.authorizer inputHoldingCids - transferLegs = map allocationToTransferEventLeg allocation.transferLegs + transferLegSides = map allocationToTransferEventLegSide allocation.transferLegSides outputHoldingCids observers = accountParties allocation.authorizer extraArgs = ExtraArgs with @@ -153,20 +157,22 @@ logTransfer -> Update () logTransfer eventLogCid transfer senderOutputHoldingCids receiverOutputHoldingCids = do let admin = transfer.instrumentId.admin - let leg = TransferEventsV2.TransferLeg with + let leg side otherside = TransferEventsV2.TransferLegSide with transferLegId = "" -- no disambiguation needed for single-leg transfers - sender = transfer.sender - receiver = transfer.receiver - instrumentId = transfer.instrumentId + side + otherside + instrumentId = transfer.instrumentId.id amount = transfer.amount meta = transfer.meta + let transferIn = leg TransferEventsV2.ReceiverSide transfer.sender + let transferOut = leg TransferEventsV2.SenderSide transfer.receiver if transfer.sender == transfer.receiver then do void $ exercise eventLogCid TransferEventsV2.EventLog_HoldingsChange with admin account = transfer.sender inputHoldingCids = transfer.inputHoldingCids - transferLegs = [leg] + transferLegSides = [transferOut, transferIn] outputHoldingCids = senderOutputHoldingCids ++ receiverOutputHoldingCids observers = accountParties transfer.sender extraArgs = emptyExtraArgs @@ -175,7 +181,7 @@ logTransfer eventLogCid transfer senderOutputHoldingCids receiverOutputHoldingCi admin account = transfer.sender inputHoldingCids = transfer.inputHoldingCids - transferLegs = [leg] + transferLegSides = [transferOut] outputHoldingCids = senderOutputHoldingCids observers = accountParties transfer.sender extraArgs = emptyExtraArgs @@ -183,7 +189,7 @@ logTransfer eventLogCid transfer senderOutputHoldingCids receiverOutputHoldingCi admin account = transfer.receiver inputHoldingCids = [] - transferLegs = [leg] + transferLegSides = [transferIn] outputHoldingCids = receiverOutputHoldingCids observers = accountParties transfer.receiver extraArgs = emptyExtraArgs @@ -195,7 +201,7 @@ logMergeSplit eventLogCid admin account inputHoldingCids reason outputHoldingCid admin account inputHoldingCids - transferLegs = [] + transferLegSides = [] outputHoldingCids observers = accountParties account extraArgs = ExtraArgs with