From 8ae6f8193732781e3c30994925b0307a0db46c2e Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Tue, 4 Nov 2025 14:41:31 +0000 Subject: [PATCH 01/34] cip-82: add DevelopmentFundCoupon template Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/Amulet.daml | 31 +++++++++++++++------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/daml/splice-amulet/daml/Splice/Amulet.daml b/daml/splice-amulet/daml/Splice/Amulet.daml index d3366d4190..a9179c1038 100644 --- a/daml/splice-amulet/daml/Splice/Amulet.daml +++ b/daml/splice-amulet/daml/Splice/Amulet.daml @@ -81,7 +81,7 @@ data SvRewardCoupon_ArchiveAsBeneficiaryResult = SvRewardCoupon_ArchiveAsBenefic data UnclaimedActivityRecord_ArchiveAsBeneficiaryResult = UnclaimedActivityRecord_ArchiveAsBeneficiaryResult -data UnclaimedActivityRecord_DsoExpireResult = UnclaimedActivityRecord_DsoExpireResult with +data UnclaimedActivityRecord_DsoExpireResult = UnclaimedActivityRecord_DsoExpireResult with unclaimedRewardCid : ContractId UnclaimedReward -- | A amulet, which can be locked and whose amount expires over time. @@ -383,6 +383,17 @@ template SvRewardCoupon with do return SvRewardCoupon_ArchiveAsBeneficiaryResult +-- | A coupon for the Foundation’s share of mint emissions under the Development Fund. +template DevelopmentFundCoupon + with + dso : Party + amount : Decimal -- ^ The total amount of `Amulet` to mint on collection. + round : Round -- ^ The round when the coupon is created. + where + signatory dso + ensure amount > 0.0 + + -- | Rewards that have not been claimed and are thus at the disposal of the foundation. template UnclaimedReward with dso : Party @@ -392,29 +403,29 @@ template UnclaimedReward with signatory dso --- | A record of activity that can be minted by the beneficiary. --- Note that these do not come out of the per-round issuance but are instead created by burning --- UnclaimedRewardCoupon as defined through a vote by the SVs. That's also why expiry is a separate +-- | A record of activity that can be minted by the beneficiary. +-- Note that these do not come out of the per-round issuance but are instead created by burning +-- UnclaimedRewardCoupon as defined through a vote by the SVs. That's also why expiry is a separate -- time-based expiry instead of being tied to a round like the other activity records. template UnclaimedActivityRecord with dso : Party beneficiary : Party -- ^ The owner of the `Amulet` to be minted. amount : Decimal -- ^ The amount of `Amulet` to be minted. - reason : Text -- ^ A reason to mint the `Amulet`. - expiresAt : Time -- ^ Selected timestamp defining the lifetime of the contract. - where + reason : Text -- ^ A reason to mint the `Amulet`. + expiresAt : Time -- ^ Selected timestamp defining the lifetime of the contract. + where signatory dso observer beneficiary ensure amount > 0.0 choice UnclaimedActivityRecord_DsoExpire : UnclaimedActivityRecord_DsoExpireResult controller dso - do + do assertDeadlineExceeded "UnclaimedActivityRecord.expiresAt" expiresAt unclaimedRewardCid <- create UnclaimedReward with dso; amount pure UnclaimedActivityRecord_DsoExpireResult with unclaimedRewardCid - + requireAmuletExpiredForAllOpenRounds : ContractId OpenMiningRound -> Amulet -> Update () requireAmuletExpiredForAllOpenRounds roundCid amulet = do @@ -456,4 +467,4 @@ instance HasCheckedFetch FeaturedAppActivityMarker ForDso where contractGroupId FeaturedAppActivityMarker {..} = ForDso with dso instance HasCheckedFetch UnclaimedActivityRecord ForOwner where - contractGroupId UnclaimedActivityRecord{..} = ForOwner with dso; owner = beneficiary \ No newline at end of file + contractGroupId UnclaimedActivityRecord{..} = ForOwner with dso; owner = beneficiary From 59ebaa58dea3764cfdc55dcc4a60c8eae001547b Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Tue, 4 Nov 2025 14:43:07 +0000 Subject: [PATCH 02/34] cip-82: add DevelopmentFundConfig to AmuletConfig Signed-off-by: Jose Velasco - IEU --- .../Scripts/TestLockAndAmuletExpiry.daml | 1 + .../daml/Splice/AmuletConfig.daml | 27 +++++++++++++++++-- daml/splice-util/daml/Splice/Util.daml | 3 +++ .../Registries/AmuletRegistry/Parameters.daml | 2 ++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml b/daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml index f39dae024a..e862cecab0 100644 --- a/daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml +++ b/daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml @@ -33,6 +33,7 @@ scaleAmuletConfig amuletPrice config = AmuletConfig with packageConfig = config.packageConfig transferPreapprovalFee = fmap (/ amuletPrice) config.transferPreapprovalFee featuredAppActivityMarkerAmount = fmap (/ amuletPrice) config.featuredAppActivityMarkerAmount + developmentFundConfig = config.developmentFundConfig test : Script () test = script do diff --git a/daml/splice-amulet/daml/Splice/AmuletConfig.daml b/daml/splice-amulet/daml/Splice/AmuletConfig.daml index abb4dc9af0..cefd2f516f 100644 --- a/daml/splice-amulet/daml/Splice/AmuletConfig.daml +++ b/daml/splice-amulet/daml/Splice/AmuletConfig.daml @@ -45,6 +45,7 @@ data AmuletConfig unit = AmuletConfig with -- that should be used for command submissions. transferPreapprovalFee : Optional Decimal -- ^ Fee for keeping a transfer pre-approval around. featuredAppActivityMarkerAmount : Optional Decimal -- ^ $-amount used for the conversion from FeaturedAppActivityMarker -> AppRewardCoupon + developmentFundConfig : Optional DevelopmentFundConfig -- ^ Configuration defining the beneficiary and allocation rate for the Development Fund. deriving (Eq, Show) -- $1/year specified as a daily rate @@ -61,7 +62,8 @@ validAmuletConfig AmuletConfig , tickDuration , packageConfig , transferPreapprovalFee - , featuredAppActivityMarkerAmount} + , featuredAppActivityMarkerAmount + , developmentFundConfig } = validTransferConfig transferConfig && validIssuanceCurve issuanceCurve && @@ -69,7 +71,8 @@ validAmuletConfig AmuletConfig tickDuration > days 0 && validPackageConfig packageConfig && optional True (>= 0.0) transferPreapprovalFee && - optional True (>= 0.0) featuredAppActivityMarkerAmount + optional True (>= 0.0) featuredAppActivityMarkerAmount && + optional True validDevelopmentFundConfig developmentFundConfig validTransferConfig : TransferConfig unit -> Bool validTransferConfig TransferConfig @@ -111,6 +114,20 @@ data PackageConfig = PackageConfig validPackageConfig : PackageConfig -> Bool validPackageConfig _ = True +-- | The development fund config defines the beneficiary party and +-- the percentage of each mint emission allocated to the Foundation-governed fund. +-- +-- See Splice.Scripts.Parameters for concrete values. +data DevelopmentFundConfig = DevelopmentFundConfig + with + beneficiary : Party + percentage : Decimal + deriving (Show, Eq) + +validDevelopmentFundConfig : DevelopmentFundConfig -> Bool +validDevelopmentFundConfig DevelopmentFundConfig { percentage } = + percentage >= 0.0 && percentage <= 1.0 + instance Patchable (AmuletConfig USD) where patch new base current = AmuletConfig with transferConfig = patch new.transferConfig base.transferConfig current.transferConfig @@ -120,6 +137,7 @@ instance Patchable (AmuletConfig USD) where packageConfig = patch new.packageConfig base.packageConfig current.packageConfig transferPreapprovalFee = patch new.transferPreapprovalFee base.transferPreapprovalFee current.transferPreapprovalFee featuredAppActivityMarkerAmount = patch new.featuredAppActivityMarkerAmount base.featuredAppActivityMarkerAmount current.featuredAppActivityMarkerAmount + developmentFundConfig = patch new.developmentFundConfig base.developmentFundConfig current.developmentFundConfig instance Patchable (TransferConfig USD) where patch new base current = TransferConfig with @@ -140,3 +158,8 @@ instance Patchable PackageConfig where validatorLifecycle = patch new.validatorLifecycle base.validatorLifecycle current.validatorLifecycle wallet = patch new.wallet base.wallet current.wallet walletPayments = patch new.walletPayments base.walletPayments current.walletPayments + +instance Patchable DevelopmentFundConfig where + patch new base current = DevelopmentFundConfig with + beneficiary = patch new.beneficiary base.beneficiary current.beneficiary + percentage = patch new.percentage base.percentage current.percentage diff --git a/daml/splice-util/daml/Splice/Util.daml b/daml/splice-util/daml/Splice/Util.daml index 80ee7987cb..aa9a17a0cd 100644 --- a/daml/splice-util/daml/Splice/Util.daml +++ b/daml/splice-util/daml/Splice/Util.daml @@ -206,6 +206,9 @@ instance Patchable RelTime where instance Patchable Time where patch = patchScalar +instance Patchable Party where + patch = patchScalar + mapDifference : Ord k => Map k a -> Map k a -> Map k k mapDifference = Map.merge (\_ _ -> None) (\k _ -> Some k) (\_ _ _ -> None) diff --git a/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml b/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml index a806f1c567..eb6ff0f6ab 100644 --- a/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml +++ b/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml @@ -88,6 +88,8 @@ defaultAmuletConfig = AmuletConfig with -- Amount of the AppRewardCoupon contract that a FeaturedAppActivityMarker is converted to. featuredAppActivityMarkerAmount = Some defaultFeaturedAppActivityMarkerAmount + developmentFundConfig = None + -- | Default configuration schedule with single current amulet config defaultAmuletConfigSchedule : Schedule Time (AmuletConfig USD) defaultAmuletConfigSchedule = Schedule with From 33acab321269cd9319f1a2220b1886e2305eb3b2 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Tue, 4 Nov 2025 15:51:21 +0000 Subject: [PATCH 03/34] cip-82: add InputDevelopmentFundCoupon to TransferInput Signed-off-by: Jose Velasco - IEU --- .../Scripts/TestAmuletRulesTransfer.daml | 1 + .../Splice/Scripts/TestDesignExample.daml | 1 + daml/splice-amulet/daml/Splice/Amulet.daml | 4 + .../daml/Splice/Amulet/TokenApiUtils.daml | 5 +- .../daml/Splice/AmuletRules.daml | 78 +++++++++++++++---- 5 files changed, 73 insertions(+), 16 deletions(-) diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml b/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml index 50d504a95b..699a782c8c 100644 --- a/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml +++ b/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml @@ -110,6 +110,7 @@ testUsageFees = do inputUnclaimedActivityRecordAmount = Some 0.0 inputValidatorFaucetAmount = Some 0.0 inputSvRewardAmount = 0.0 + inputDevelopmentFundAmount = Some 0.0 inputAmuletAmount = 100.0 balanceChanges = Map.empty holdingFees = 0.0 -- no holding fees charged on transfer diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/TestDesignExample.daml b/daml/splice-amulet-test/daml/Splice/Scripts/TestDesignExample.daml index 05ba573799..ccf1084c82 100644 --- a/daml/splice-amulet-test/daml/Splice/Scripts/TestDesignExample.daml +++ b/daml/splice-amulet-test/daml/Splice/Scripts/TestDesignExample.daml @@ -158,6 +158,7 @@ test_designExample= do inputUnclaimedActivityRecordAmount = Some 0.0 inputValidatorFaucetAmount = Some 0.0 inputSvRewardAmount = 0.0 + inputDevelopmentFundAmount = Some 0.0 inputAmuletAmount = refreshAmuletAmount + lockedAmuletAmount balanceChanges = Map.empty holdingFees diff --git a/daml/splice-amulet/daml/Splice/Amulet.daml b/daml/splice-amulet/daml/Splice/Amulet.daml index a9179c1038..b71d15c9fe 100644 --- a/daml/splice-amulet/daml/Splice/Amulet.daml +++ b/daml/splice-amulet/daml/Splice/Amulet.daml @@ -468,3 +468,7 @@ instance HasCheckedFetch FeaturedAppActivityMarker ForDso where instance HasCheckedFetch UnclaimedActivityRecord ForOwner where contractGroupId UnclaimedActivityRecord{..} = ForOwner with dso; owner = beneficiary + +instance HasCheckedFetch DevelopmentFundCoupon ForDso where + contractGroupId DevelopmentFundCoupon{..} = ForDso with dso + diff --git a/daml/splice-amulet/daml/Splice/Amulet/TokenApiUtils.daml b/daml/splice-amulet/daml/Splice/Amulet/TokenApiUtils.daml index 1b8f738c20..bd339412e3 100644 --- a/daml/splice-amulet/daml/Splice/Amulet/TokenApiUtils.daml +++ b/daml/splice-amulet/daml/Splice/Amulet/TokenApiUtils.daml @@ -43,7 +43,7 @@ nonZeroMetadata k n m -- | Add an metadata entry for an optional value if it is non-zero number. optionalNonZeroMetadata : (Eq a, Additive a, Show a) => Text -> Optional a -> TextMap Text -> TextMap Text -optionalNonZeroMetadata k optN m = +optionalNonZeroMetadata k optN m = case optN of None -> m Some n -> nonZeroMetadata k n m @@ -75,6 +75,9 @@ appRewardBeneficiariesMetaKey = amuletPrefix <> "app-reward-beneficiaries" appRewardBeneficiaryWeightsMetaKey : Text appRewardBeneficiaryWeightsMetaKey = amuletPrefix <> "app-reward-beneficiary-weights" +developmentFundAmountMetaKey : Text +developmentFundAmountMetaKey = amuletPrefix <> "development-fund" + -- Splice API Metadata keys --------------------------- diff --git a/daml/splice-amulet/daml/Splice/AmuletRules.daml b/daml/splice-amulet/daml/Splice/AmuletRules.daml index 392e25fa9f..35f98f3027 100644 --- a/daml/splice-amulet/daml/Splice/AmuletRules.daml +++ b/daml/splice-amulet/daml/Splice/AmuletRules.daml @@ -25,7 +25,7 @@ import Splice.Api.Token.MetadataV1 as Api.Token.MetadataV1 import Splice.Api.Token.HoldingV1 qualified as Api.Token.HoldingV1 import Splice.Amulet import Splice.Amulet.TokenApiUtils -import Splice.AmuletConfig (AmuletConfig(..), TransferConfig(..), validAmuletConfig, defaultTransferPreapprovalFee) +import Splice.AmuletConfig (AmuletConfig(..), DevelopmentFundConfig(..), TransferConfig(..), validAmuletConfig, defaultTransferPreapprovalFee) import qualified Splice.AmuletConfig as Unit import Splice.Schedule import Splice.Expiry @@ -128,11 +128,13 @@ template AmuletRules -- one under their control. controller Set.toList (transferControllers transfer) do + now <- getTime checkExpectedDso dso expectedDso let rewardsConfig = RewardsIssuanceConfig with issueAppRewards = True issueValidatorRewards = True - executeTransfer rewardsConfig context dso transfer + configUsd = getValueAsOf now configSchedule + executeTransfer rewardsConfig context dso configUsd.developmentFundConfig transfer nonconsuming choice AmuletRules_CreateExternalPartySetupProposal : AmuletRules_CreateExternalPartySetupProposalResult -- ^ Propose to host an external party @@ -155,7 +157,8 @@ template AmuletRules require "preapprovalExpiresAt is not in the past" (preapprovalExpiresAt > now) let configUsd = getValueAsOf now configSchedule (amuletPaid, _) <- computeTransferPreapprovalFee (preapprovalExpiresAt `subTime` now) configUsd context.context dso - (transferResult, meta) <- splitAndBurn validator amuletPaid inputs context.context dso "create ExternalPartySetupProposal" + (transferResult, meta) <- + splitAndBurn validator amuletPaid inputs context.context dso "create ExternalPartySetupProposal" configUsd.developmentFundConfig proposalCid <- create ExternalPartySetupProposal with user validator @@ -183,7 +186,8 @@ template AmuletRules require "expiresAt is not in the past" (expiresAt > now) let configUsd = getValueAsOf now configSchedule (amuletPaid, _) <- computeTransferPreapprovalFee (expiresAt `subTime` now) configUsd context.context dso - (transferResult, meta) <- splitAndBurn provider amuletPaid inputs context.context dso "create TransferPreapproval" + (transferResult, meta) <- + splitAndBurn provider amuletPaid inputs context.context dso "create TransferPreapproval" configUsd.developmentFundConfig transferPreapprovalCid <- create TransferPreapproval with receiver provider @@ -222,7 +226,8 @@ template AmuletRules let transferConfigAmulet = scaleFees (1.0 / openRound.amuletPrice) openRound.transferConfigUsd (trafficCostAmulet, trafficCostUsd) <- computeSynchronizerFees dso provider trafficAmount this context -- We want to burn exactly trafficCostAmulet, and createFee is already burnt by the self-transfer itself, so we subtract the createFee from the amount we split off and burn explicitly. - (transferResult, meta) <- splitAndBurn provider (trafficCostAmulet - transferConfigAmulet.createFee.fee) inputs context dso "traffic purchase" + (transferResult, meta) <- + splitAndBurn provider (trafficCostAmulet - transferConfigAmulet.createFee.fee) inputs context dso "traffic purchase" configUsd.developmentFundConfig -- create a new MemberTraffic contract for the purchased traffic purchasedTraffic <- create MemberTraffic with dso @@ -716,11 +721,13 @@ data RewardsIssuanceConfig = RewardsIssuanceConfig with issueValidatorRewards : Bool -- | Execute a transfer. -executeTransfer : RewardsIssuanceConfig -> TransferContext -> Party -> Transfer -> Update TransferResult -executeTransfer config context dso t = do +executeTransfer + : RewardsIssuanceConfig -> TransferContext -> Party -> Optional DevelopmentFundConfig -> Transfer + -> Update TransferResult +executeTransfer config context dso optFundConfig t = do -- compute summaries csum <- summarizeAndValidateContext context dso t - isum <- summarizeAndConsumeInputs csum dso t.sender t.inputs + isum <- summarizeAndConsumeInputs csum dso t.sender t.inputs optFundConfig osum <- preprocessOutputs csum.config t.sender t.outputs summary <- summarizeTransfer t.sender csum.openRound csum.config isum osum -- check that overall transfer constraints are satisfied @@ -749,6 +756,7 @@ executeTransfer config context dso t = do nonZeroMetadata appRewardAmountMetaKey summary.inputAppRewardAmount $ nonZeroMetadata validatorRewardAmountMetaKey summary.inputValidatorRewardAmount $ optionalNonZeroMetadata unclaimedActivityRecordAmountMetaKey summary.inputUnclaimedActivityRecordAmount $ + optionalNonZeroMetadata developmentFundAmountMetaKey summary.inputDevelopmentFundAmount $ TextMap.empty return TransferResult with @@ -778,6 +786,9 @@ data TransferInputsSummary = TransferInputsSummary with totalUnclaimedActivityRecordAmount : Optional Decimal -- ^ Note: Made optional as the addition of this field is checked by the upgrade checker -- on package upload because `TransferInputsSummary` is serializable. + totalDevelopmentFundAmount : Optional Decimal + -- ^ Note: Made optional as the addition of this field is checked by the upgrade checker + -- on package upload because `TransferInputsSummary` is serializable. deriving (Eq, Show) type TransferOutputsSummary = [PreprocessedTransferOutput] @@ -831,11 +842,12 @@ getIssuingMiningRound csum round = do Some issuingRound -> pure issuingRound summarizeAndConsumeInputs - : TransferContextSummary -> Party -> Party -> [TransferInput] -> Update TransferInputsSummary -summarizeAndConsumeInputs csum dso sender inps = do + : TransferContextSummary -> Party -> Party -> [TransferInput] -> Optional DevelopmentFundConfig -> Update TransferInputsSummary +summarizeAndConsumeInputs csum dso sender inps optFundConfig = do foldlA (summarizeAndConsumeInput csum.openRound.round) initialSummary inps where forOwner = ForOwner with dso; owner = sender + forDso = ForDso with dso initialSummary = TransferInputsSummary with totalAmuletAmount = 0.0 @@ -847,6 +859,7 @@ summarizeAndConsumeInputs csum dso sender inps = do amountArchivedAsOfRoundZero = 0.0 changeToHoldingFeesRate = 0.0 totalUnclaimedActivityRecordAmount = Some 0.0 + totalDevelopmentFundAmount = Some 0.0 summarizeAndConsumeInput _round s (InputAmulet amuletCid) = do amulet <- fetchAndArchive forOwner amuletCid @@ -863,6 +876,7 @@ summarizeAndConsumeInputs csum dso sender inps = do totalHoldingFees = s.totalHoldingFees amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero + getValueAsOfRound0 amulet.amount changeToHoldingFeesRate = s.changeToHoldingFeesRate - amulet.amount.ratePerRound.rate + totalDevelopmentFundAmount = s.totalDevelopmentFundAmount summarizeAndConsumeInput _round s (InputAppRewardCoupon couponCid) = do coupon <- fetchAndArchive forOwner couponCid @@ -881,10 +895,11 @@ summarizeAndConsumeInputs csum dso sender inps = do totalHoldingFees = s.totalHoldingFees amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero changeToHoldingFeesRate = s.changeToHoldingFeesRate + totalDevelopmentFundAmount = s.totalDevelopmentFundAmount summarizeAndConsumeInput _round s (InputValidatorRewardCoupon couponCid) = do -- we must and do use the validator right to archive the coupon of the user - coupon <- fetchButArchiveLater (ForDso with dso) couponCid + coupon <- fetchButArchiveLater forDso couponCid do rightCid <- getValidatorRight csum coupon.user exercise couponCid ValidatorRewardCoupon_ArchiveAsValidator with @@ -902,6 +917,7 @@ summarizeAndConsumeInputs csum dso sender inps = do totalHoldingFees = s.totalHoldingFees amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero changeToHoldingFeesRate = s.changeToHoldingFeesRate + totalDevelopmentFundAmount = s.totalDevelopmentFundAmount summarizeAndConsumeInput _round s (InputSvRewardCoupon couponCid) = do -- we use the SvRewardCoupon_ArchiveAsBeneficiary choice to signal the archival of the coupon @@ -920,6 +936,7 @@ summarizeAndConsumeInputs csum dso sender inps = do totalHoldingFees = s.totalHoldingFees amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero changeToHoldingFeesRate = s.changeToHoldingFeesRate + totalDevelopmentFundAmount = s.totalDevelopmentFundAmount summarizeAndConsumeInput _round s (InputValidatorLivenessActivityRecord recordCid) = do record <- fetchAndArchive forOwner recordCid @@ -936,6 +953,7 @@ summarizeAndConsumeInputs csum dso sender inps = do totalHoldingFees = s.totalHoldingFees amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero changeToHoldingFeesRate = s.changeToHoldingFeesRate + totalDevelopmentFundAmount = s.totalDevelopmentFundAmount summarizeAndConsumeInput _round s (ExtTransferInput _dummyUnitField optInputValidatorFaucetCoupon) = do optional (pure s) (summarizeAndConsumeValidatorFaucetInput s) optInputValidatorFaucetCoupon @@ -954,6 +972,26 @@ summarizeAndConsumeInputs csum dso sender inps = do totalHoldingFees = s.totalHoldingFees amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero changeToHoldingFeesRate = s.changeToHoldingFeesRate + totalDevelopmentFundAmount = s.totalDevelopmentFundAmount + + summarizeAndConsumeInput _round s (InputDevelopmentFundCoupon couponCid) = do + case optFundConfig of + None -> abort "DevelopmentFundCoupon cannot be consumed without AmuletConfig/DevelopmentFundConfig configured" + Some config -> + require "sender matches the beneficiary defined in AmuletConfig/DevelopmentFundConfig" $ + sender == config.beneficiary + coupon <- fetchAndArchive forDso couponCid + return TransferInputsSummary with + totalAmuletAmount = s.totalAmuletAmount + totalAppRewardAmount = s.totalAppRewardAmount + totalValidatorRewardAmount = s.totalValidatorRewardAmount + totalUnclaimedActivityRecordAmount = s.totalUnclaimedActivityRecordAmount + totalValidatorFaucetAmount = s.totalValidatorFaucetAmount + totalSvRewardAmount = s.totalSvRewardAmount + totalHoldingFees = s.totalHoldingFees + amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero + changeToHoldingFeesRate = s.changeToHoldingFeesRate + totalDevelopmentFundAmount = (+ coupon.amount) <$> s.totalDevelopmentFundAmount summarizeAndConsumeValidatorFaucetInput s couponCid = do coupon <- fetchAndArchive forOwner couponCid @@ -970,6 +1008,7 @@ summarizeAndConsumeInputs csum dso sender inps = do totalHoldingFees = s.totalHoldingFees amountArchivedAsOfRoundZero = s.amountArchivedAsOfRoundZero changeToHoldingFeesRate = s.changeToHoldingFeesRate + totalDevelopmentFundAmount = s.totalDevelopmentFundAmount -- | Deduplicate lock-holders to store them and charge for them at most once dedupOutputLockHolders : TransferOutput -> TransferOutput @@ -1029,6 +1068,7 @@ summarizeTransfer sender openRound transferConfigAmulet inp preprocessedOutputs + fromOptional 0.0 inp.totalUnclaimedActivityRecordAmount + inp.totalValidatorFaucetAmount + inp.totalSvRewardAmount + + fromOptional 0.0 inp.totalDevelopmentFundAmount - totalOutputAmount - sum outputFees senderChangeFee = min transferConfigAmulet.createFee.fee leftOverAmount senderChangeAmount = leftOverAmount - senderChangeFee @@ -1054,6 +1094,7 @@ summarizeTransfer sender openRound transferConfigAmulet inp preprocessedOutputs inputUnclaimedActivityRecordAmount = inp.totalUnclaimedActivityRecordAmount inputValidatorFaucetAmount = Some inp.totalValidatorFaucetAmount inputSvRewardAmount = inp.totalSvRewardAmount + inputDevelopmentFundAmount = inp.totalDevelopmentFundAmount holdingFees = inp.totalHoldingFees outputFees senderChangeFee @@ -1245,6 +1286,7 @@ data TransferInput -- ^ Added in CIP-3. Optional validator faucet coupon input into this transfer. | InputValidatorLivenessActivityRecord (ContractId ValidatorLivenessActivityRecord) | InputUnclaimedActivityRecord (ContractId UnclaimedActivityRecord) + | InputDevelopmentFundCoupon (ContractId DevelopmentFundCoupon) deriving (Eq, Ord, Show) -- | Smart constructor for inputing validator faucet coupons into a transfer. @@ -1348,6 +1390,9 @@ data TransferSummary = TransferSummary with inputUnclaimedActivityRecordAmount : Optional Decimal -- ^ Total amount of unclaimed activity record issuance input into this transfer. -- Note: Made optional as the addition of this field is checked by the upgrade checker. + inputDevelopmentFundAmount : Optional Decimal + -- ^ Total amount of development fund coupon issuance input into this transfer. + -- Note: Made optional as the addition of this field is checked by the upgrade checker. deriving (Show, Eq) data BalanceChange = BalanceChange with @@ -1510,7 +1555,8 @@ template TransferPreapproval amuletRules <- fetchPublicReferenceData (ForDso dso) context.amuletRules (AmuletRules_Fetch dso) let configUsd = getValueAsOf now amuletRules.configSchedule (amuletPaid, _) <- computeTransferPreapprovalFee extension configUsd context.context dso - (transferResult, meta) <- splitAndBurn provider amuletPaid inputs context.context dso "renew TransferPreapproval" + (transferResult, meta) <- + splitAndBurn provider amuletPaid inputs context.context dso "renew TransferPreapproval" configUsd.developmentFundConfig transferPreapprovalCid <- create this with lastRenewedAt = now expiresAt = newExpiresAt @@ -1568,13 +1614,15 @@ computeTransferPreapprovalFee duration amuletConfig context dso = do let feeAmulet = feeUsd / contextMiningRound.amuletPrice pure (feeAmulet, feeUsd) -splitAndBurn : Party -> Decimal -> [TransferInput] -> TransferContext -> Party -> Text -> Update (TransferResult, Metadata) -splitAndBurn sender amount inputs context dso usage = do +splitAndBurn + : Party -> Decimal -> [TransferInput] -> TransferContext -> Party -> Text -> Optional DevelopmentFundConfig + -> Update (TransferResult, Metadata) +splitAndBurn sender amount inputs context dso usage optFundConfig = do -- do not create activity records as part of the transfer. We create a validator activity record for the full burn below. let rewardsConfig = RewardsIssuanceConfig with issueAppRewards = False issueValidatorRewards = False - transferResult <- executeTransfer rewardsConfig context dso Transfer with + transferResult <- executeTransfer rewardsConfig context dso optFundConfig Transfer with sender provider = sender inputs From c9eab5ef9beefe0cb00c4a256dfbd5457aca6cd0 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Tue, 4 Nov 2025 17:36:26 +0000 Subject: [PATCH 04/34] cip-82: update comment Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/AmuletRules.daml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daml/splice-amulet/daml/Splice/AmuletRules.daml b/daml/splice-amulet/daml/Splice/AmuletRules.daml index 35f98f3027..33e041cc8a 100644 --- a/daml/splice-amulet/daml/Splice/AmuletRules.daml +++ b/daml/splice-amulet/daml/Splice/AmuletRules.daml @@ -787,8 +787,8 @@ data TransferInputsSummary = TransferInputsSummary with -- ^ Note: Made optional as the addition of this field is checked by the upgrade checker -- on package upload because `TransferInputsSummary` is serializable. totalDevelopmentFundAmount : Optional Decimal - -- ^ Note: Made optional as the addition of this field is checked by the upgrade checker - -- on package upload because `TransferInputsSummary` is serializable. + -- ^ Note: Same rationale as above — made optional to ensure compatibility with + -- the upgrade checker on package upload because `TransferInputsSummary` is serializable. deriving (Eq, Show) type TransferOutputsSummary = [PreprocessedTransferOutput] From e259389457a50c6f03c4b8700f49f734d494030f Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Tue, 4 Nov 2025 17:46:52 +0000 Subject: [PATCH 05/34] cip-82: remove round from DevelopmentFundCoupon Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/Amulet.daml | 1 - 1 file changed, 1 deletion(-) diff --git a/daml/splice-amulet/daml/Splice/Amulet.daml b/daml/splice-amulet/daml/Splice/Amulet.daml index b71d15c9fe..b8b2281450 100644 --- a/daml/splice-amulet/daml/Splice/Amulet.daml +++ b/daml/splice-amulet/daml/Splice/Amulet.daml @@ -388,7 +388,6 @@ template DevelopmentFundCoupon with dso : Party amount : Decimal -- ^ The total amount of `Amulet` to mint on collection. - round : Round -- ^ The round when the coupon is created. where signatory dso ensure amount > 0.0 From adf3d4d9f360e6faf0dd20a4f2988e3ee6409fb5 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Tue, 4 Nov 2025 17:50:39 +0000 Subject: [PATCH 06/34] cip-82: add AmuletRules_MergeDevelopmentFundCoupons Signed-off-by: Jose Velasco - IEU --- .../daml/Splice/AmuletRules.daml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/daml/splice-amulet/daml/Splice/AmuletRules.daml b/daml/splice-amulet/daml/Splice/AmuletRules.daml index 33e041cc8a..fb45c52070 100644 --- a/daml/splice-amulet/daml/Splice/AmuletRules.daml +++ b/daml/splice-amulet/daml/Splice/AmuletRules.daml @@ -76,6 +76,9 @@ data AmuletRules_ClaimExpiredRewardsResult = AmuletRules_ClaimExpiredRewardsResu data AmuletRules_MergeUnclaimedRewardsResult = AmuletRules_MergeUnclaimedRewardsResult with unclaimedRewardCid : ContractId UnclaimedReward +data AmuletRules_MergeDevelopmentFundCouponsResult = AmuletRules_MergeDevelopmentFundCouponsResult with + developmentFundCouponCid : ContractId DevelopmentFundCoupon + data AmuletRules_SetConfigResult = AmuletRules_SetConfigResult with newAmuletRules : ContractId AmuletRules @@ -584,6 +587,26 @@ template AmuletRules return AmuletRules_MergeUnclaimedRewardsResult with .. + -- Batch merge of development fund coupons + nonconsuming choice AmuletRules_MergeDevelopmentFundCoupons : AmuletRules_MergeDevelopmentFundCouponsResult + with + developmentFundCouponCids : [ContractId DevelopmentFundCoupon] + controller dso + do + require "More than one development fund coupon contracts" (length developmentFundCouponCids > 1) + + -- archive all given coupons + archivedAmounts <- forA developmentFundCouponCids $ \developmentFundCouponCid -> do + developmentFundCoupon <- fetchAndArchive (ForDso with dso) developmentFundCouponCid + pure developmentFundCoupon.amount + + -- create a new development fund coupon over the total + developmentFundCouponCid <- create DevelopmentFundCoupon with + dso + amount = sum archivedAmounts + + return AmuletRules_MergeDevelopmentFundCouponsResult with .. + -- This allows fetchByKey-style fetches if you have readAs -- but not actAs claims. nonconsuming choice AmuletRules_Fetch: AmuletRules From a8f2c02694f8e48af928ec4c06110cc28f800742 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Tue, 4 Nov 2025 17:58:41 +0000 Subject: [PATCH 07/34] cip-82: add DsoRules_MergeDevelopmentFundCoupons Signed-off-by: Jose Velasco - IEU --- .../daml/Splice/DsoRules.daml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/daml/splice-dso-governance/daml/Splice/DsoRules.daml b/daml/splice-dso-governance/daml/Splice/DsoRules.daml index 71990a32ea..5bbccead65 100644 --- a/daml/splice-dso-governance/daml/Splice/DsoRules.daml +++ b/daml/splice-dso-governance/daml/Splice/DsoRules.daml @@ -253,6 +253,9 @@ data DsoRules_ClaimExpiredRewardsResult = DsoRules_ClaimExpiredRewardsResult wit data DsoRules_MergeUnclaimedRewardsResult = DsoRules_MergeUnclaimedRewardsResult with unclaimedReward: ContractId UnclaimedReward +data DsoRules_MergeDevelopmentFundCouponsResult = DsoRules_MergeDevelopmentFundCouponsResult with + developmentFundCoupon : ContractId DevelopmentFundCoupon + data DsoRules_MiningRound_CloseResult = DsoRules_MiningRound_CloseResult with closedRound : ContractId ClosedMiningRound @@ -1318,6 +1321,19 @@ template DsoRules with return DsoRules_MergeUnclaimedRewardsResult with unclaimedReward = result.unclaimedRewardCid + -- Batch merge of of development fund coupons + nonconsuming choice DsoRules_MergeDevelopmentFundCoupons : DsoRules_MergeDevelopmentFundCouponsResult + with + amuletRulesCid : ContractId AmuletRules + developmentFundCouponCids : [ContractId DevelopmentFundCoupon] + sv : Optional Party + controller sv + do + _ <- getAndValidateSvParty this sv + result <- exercise amuletRulesCid AmuletRules_MergeDevelopmentFundCoupons with .. + return DsoRules_MergeDevelopmentFundCouponsResult with + developmentFundCoupon = result.developmentFundCouponCid + nonconsuming choice DsoRules_MiningRound_Close : DsoRules_MiningRound_CloseResult with amuletRulesCid : ContractId AmuletRules From 634cfd140d0660b0f0a743bdd34b5ac4ec4c96b3 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Wed, 5 Nov 2025 12:50:28 +0000 Subject: [PATCH 08/34] cip-82: change issuance computation to deduct the fund issuance + create a DevelopmentFundCoupon contract in AmuletRules_MiningRound_StartIssuing Signed-off-by: Jose Velasco - IEU --- .../Splice/Scripts/UnitTests/Issuance.daml | 24 ++++++++++++------- .../daml/Splice/AmuletRules.daml | 18 +++++++++++--- daml/splice-amulet/daml/Splice/Issuance.daml | 22 +++++++++++++---- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml b/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml index e32b7b3edb..8ec80eef07 100644 --- a/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml +++ b/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml @@ -66,6 +66,7 @@ expectedParameters_E1_0_0p5 = IssuingRoundParameters with unclaimedValidatorRewards = 7551.7503805175 unclaimedAppRewards = 68395.2511415525 unclaimedSvRewards = 0.0000000001 + amuletsToIssueToDevelopmentFund = 0.0 expectedParameters_E1_0p5_1p5 : IssuingRoundParameters expectedParameters_E1_0p5_1p5 = IssuingRoundParameters with @@ -77,6 +78,7 @@ expectedParameters_E1_0p5_1p5 = IssuingRoundParameters with unclaimedValidatorRewards = 15162.1004566210 unclaimedAppRewards = 106447.0015220700 unclaimedSvRewards = 0.000000004 + amuletsToIssueToDevelopmentFund = 0.0 expectedParameters_E1_1p5_5 : IssuingRoundParameters expectedParameters_E1_1p5_5 = IssuingRoundParameters with @@ -88,6 +90,7 @@ expectedParameters_E1_1p5_5 = IssuingRoundParameters with unclaimedValidatorRewards = 3746.5753424658 unclaimedAppRewards = 72200.4261796042 unclaimedSvRewards = 0.0 + amuletsToIssueToDevelopmentFund = 0.0 expectedParameters_E1_5_10 : IssuingRoundParameters expectedParameters_E1_5_10 = IssuingRoundParameters with @@ -99,6 +102,7 @@ expectedParameters_E1_5_10 = IssuingRoundParameters with unclaimedValidatorRewards = 0.0000000017 unclaimedAppRewards = 19879.2694063927 unclaimedSvRewards = 0.0 + amuletsToIssueToDevelopmentFund = 0.0 expectedParameters_E1_10plus : IssuingRoundParameters expectedParameters_E1_10plus = IssuingRoundParameters with @@ -110,16 +114,17 @@ expectedParameters_E1_10plus = IssuingRoundParameters with unclaimedValidatorRewards = 0.0 unclaimedAppRewards = 0.0000000152 unclaimedSvRewards = 0.0000000023 + amuletsToIssueToDevelopmentFund = 0.0 testE1 : Script () testE1 = script do validateOpenMiningRoundSummary summaryExample1 - expectedParameters_E1_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExample1 - expectedParameters_E1_0p5_1p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0p5_1p5 summaryExample1 - expectedParameters_E1_1p5_5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_1p5_5 summaryExample1 - expectedParameters_E1_5_10 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_5_10 summaryExample1 - expectedParameters_E1_10plus === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExample1 + expectedParameters_E1_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExample1 None + expectedParameters_E1_0p5_1p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0p5_1p5 summaryExample1 None + expectedParameters_E1_1p5_5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_1p5_5 summaryExample1 None + expectedParameters_E1_5_10 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_5_10 summaryExample1 None + expectedParameters_E1_10plus === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExample1 None -- Example 2: all zeros @@ -143,12 +148,13 @@ expectedParameters_NoActivity_0_0p5 = IssuingRoundParameters with unclaimedValidatorRewards = 38051.7503805175 unclaimedAppRewards = 114155.2511415525 unclaimedSvRewards = 608828.0060882801 + amuletsToIssueToDevelopmentFund = 0.0 testNoActivity : Script () testNoActivity = script do validateOpenMiningRoundSummary summaryExamplNoActivity - expectedParameters_NoActivity_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExamplNoActivity + expectedParameters_NoActivity_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExamplNoActivity None -- Example 3: low activity @@ -173,12 +179,13 @@ expectedParameters_E3_0_0p5 = IssuingRoundParameters with unclaimedValidatorRewards = 9511.7503805175 unclaimedAppRewards = 104095.2511415525 unclaimedSvRewards = 0.0000000001 + amuletsToIssueToDevelopmentFund = 0.0 testE4 : Script () testE4 = script do validateOpenMiningRoundSummary summaryExample3 - expectedParameters_E3_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExample3 + expectedParameters_E3_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExample3 None -- Example 4: millions of SV reward weight @@ -203,12 +210,13 @@ expectedParameters_LargeSvRewardWeight_10plus = IssuingRoundParameters with unclaimedAppRewards = 35673.5159817352 unclaimedValidatorRewards = 9512.9375951294 unclaimedSvRewards = 0.0 + amuletsToIssueToDevelopmentFund = 0.0 testLargeSvRewardWeight : Script () testLargeSvRewardWeight = script do validateOpenMiningRoundSummary summaryExamplNoActivity - let actual = computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExampleLargeSvRewardWeight + let actual = computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExampleLargeSvRewardWeight None expectedParameters_LargeSvRewardWeight_10plus === actual diff --git a/daml/splice-amulet/daml/Splice/AmuletRules.daml b/daml/splice-amulet/daml/Splice/AmuletRules.daml index fb45c52070..24800b13b6 100644 --- a/daml/splice-amulet/daml/Splice/AmuletRules.daml +++ b/daml/splice-amulet/daml/Splice/AmuletRules.daml @@ -64,6 +64,7 @@ data AmuletRules_AdvanceOpenMiningRoundsResult = AmuletRules_AdvanceOpenMiningRo data AmuletRules_MiningRound_StartIssuingResult = AmuletRules_MiningRound_StartIssuingResult with issuingRoundCid : ContractId IssuingMiningRound + developmentFundCouponCid : Optional (ContractId DevelopmentFundCoupon) data AmuletRules_MiningRound_CloseResult = AmuletRules_MiningRound_CloseResult with closedRoundCid : ContractId ClosedMiningRound @@ -445,11 +446,14 @@ template AmuletRules summary : OpenMiningRoundSummary controller dso do + now <- getTime miningRound <- fetchAndArchive (ForDso with dso) miningRoundCid validateOpenMiningRoundSummary summary - let params = computeIssuingRoundParameters - miningRound.tickDuration miningRound.amuletPrice miningRound.issuanceConfig summary + let + optDevelopmentFundPercentage = (.percentage) <$> (getValueAsOf now configSchedule).developmentFundConfig + params = computeIssuingRoundParameters + miningRound.tickDuration miningRound.amuletPrice miningRound.issuanceConfig summary optDevelopmentFundPercentage -- record unclaimed reward contract let totalUnclaimedRewards = params.unclaimedValidatorRewards + params.unclaimedAppRewards + params.unclaimedSvRewards @@ -458,8 +462,16 @@ template AmuletRules dso amount = totalUnclaimedRewards + -- record development fund coupon contract + developmentFundCouponCid <- + if params.amuletsToIssueToDevelopmentFund > 0.0 + then + Some <$> create DevelopmentFundCoupon with + dso + amount = params.amuletsToIssueToDevelopmentFund + else pure None + -- create issuing round - now <- getTime let tickDuration = miningRound.tickDuration let opensAt = addRelTime now tickDuration let targetClosesAt = addRelTime opensAt (tickDuration + tickDuration) diff --git a/daml/splice-amulet/daml/Splice/Issuance.daml b/daml/splice-amulet/daml/Splice/Issuance.daml index 91da289785..0ca451d4d1 100644 --- a/daml/splice-amulet/daml/Splice/Issuance.daml +++ b/daml/splice-amulet/daml/Splice/Issuance.daml @@ -73,6 +73,7 @@ data IssuingRoundParameters = IssuingRoundParameters with unclaimedValidatorRewards : Decimal unclaimedSvRewards : Decimal -- ^ Can be non-zero due to rounding, or no SV having had the chance to claim their coupons. issuancePerValidatorFaucetCoupon : Decimal + amuletsToIssueToDevelopmentFund : Decimal deriving (Eq, Show) validateOpenMiningRoundSummary : CanAssert m => OpenMiningRoundSummary -> m () @@ -82,8 +83,10 @@ validateOpenMiningRoundSummary summary = do require "totalUnfeaturedAppRewardCoupons >= 0.0" (summary.totalUnfeaturedAppRewardCoupons >= 0.0) require "totalValidatorFaucetCoupons >= 0" (getTotalValidatorFaucetCoupons summary >= 0) -computeIssuingRoundParameters : RelTime -> Decimal -> IssuanceConfig -> OpenMiningRoundSummary -> IssuingRoundParameters -computeIssuingRoundParameters tickDuration amuletPrice config summary = +computeIssuingRoundParameters + : RelTime -> Decimal -> IssuanceConfig -> OpenMiningRoundSummary -> Optional Decimal + -> IssuingRoundParameters +computeIssuingRoundParameters tickDuration amuletPrice config summary optDevelopmentFundPercentage = IssuingRoundParameters with issuancePerValidatorRewardCoupon = validatorRewardIssuance.issuancePerCoupon issuancePerUnfeaturedAppRewardCoupon = unfeaturedAppIssuance.issuancePerCoupon @@ -94,9 +97,15 @@ computeIssuingRoundParameters tickDuration amuletPrice config summary = unclaimedAppRewards = featuredAppIssuance.unclaimedRewards unclaimedSvRewards issuancePerValidatorFaucetCoupon = validatorFaucetIssuance.issuancePerCoupon + amuletsToIssueToDevelopmentFund where + developmentFundPercentage = fromOptional 0.0 optDevelopmentFundPercentage + remainingEmissionPercentage = 1.0 - developmentFundPercentage + amuletsToIssueToSvs = - amuletsToIssueInRound - validatorRewardIssuance.rewardsToIssue - unfeaturedAppIssuance.rewardsToIssue + amuletsToIssueInRound * remainingEmissionPercentage + - validatorRewardIssuance.rewardsToIssue + - unfeaturedAppIssuance.rewardsToIssue issuancePerSvRewardCoupon = if summary.totalSvRewardWeight == 0 @@ -114,8 +123,11 @@ computeIssuingRoundParameters tickDuration amuletPrice config summary = intToDecimal (convertRelTimeToMicroseconds tickDuration) amuletsToIssueInRound = config.amuletToIssuePerYear / roundsPerYear + adjustedAmuletsToIssueInRound = amuletsToIssueInRound * remainingEmissionPercentage + amuletsToIssueToDevelopmentFund = amuletsToIssueInRound * developmentFundPercentage + validatorRewardIssuance = computeIssuanceTranche - (amuletsToIssueInRound * config.validatorRewardPercentage) + (adjustedAmuletsToIssueInRound * config.validatorRewardPercentage) config.validatorRewardCap summary.totalValidatorRewardCoupons @@ -126,7 +138,7 @@ computeIssuingRoundParameters tickDuration amuletPrice config summary = (intToDecimal $ getTotalValidatorFaucetCoupons summary) unfeaturedAppIssuance = computeIssuanceTranche - (amuletsToIssueInRound * config.appRewardPercentage) + (adjustedAmuletsToIssueInRound * config.appRewardPercentage) config.unfeaturedAppRewardCap (summary.totalFeaturedAppRewardCoupons + summary.totalUnfeaturedAppRewardCoupons) From 85008e8dcc355da7a4844c6ec9c80ea39c001313 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Wed, 5 Nov 2025 16:15:55 +0000 Subject: [PATCH 09/34] cip-82: add note Signed-off-by: Jose Velasco - IEU --- .../Testing/Registries/AmuletRegistry/Parameters.daml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml b/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml index eb6ff0f6ab..c385616c90 100644 --- a/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml +++ b/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml @@ -61,6 +61,13 @@ defaultAmuletDecentralizedSynchronizerConfig = AmuletDecentralizedSynchronizerCo defaultFeaturedAppActivityMarkerAmount : Decimal defaultFeaturedAppActivityMarkerAmount = 1.0 +-- Note: In the final PR, `developmentFundConfig` should not be set to None. +-- The definition of `defaultAmuletConfig` will change from +-- `defaultAmuletConfig : AmuletConfig USD` +-- to +-- `defaultAmuletConfig : Party -> AmuletConfig USD`, +-- where the `Party` parameter represents the Foundation beneficiary. +-- -- | Default proposal for issuance curve and tickDuration defaultAmuletConfig : AmuletConfig USD defaultAmuletConfig = AmuletConfig with From 50350b4b7868f8d959b31db2a8698f84eedd06d7 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Wed, 5 Nov 2025 16:52:34 +0000 Subject: [PATCH 10/34] cip-82: naming Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/AmuletRules.daml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daml/splice-amulet/daml/Splice/AmuletRules.daml b/daml/splice-amulet/daml/Splice/AmuletRules.daml index 24800b13b6..938c0fd5b3 100644 --- a/daml/splice-amulet/daml/Splice/AmuletRules.daml +++ b/daml/splice-amulet/daml/Splice/AmuletRules.daml @@ -1012,9 +1012,9 @@ summarizeAndConsumeInputs csum dso sender inps optFundConfig = do summarizeAndConsumeInput _round s (InputDevelopmentFundCoupon couponCid) = do case optFundConfig of None -> abort "DevelopmentFundCoupon cannot be consumed without AmuletConfig/DevelopmentFundConfig configured" - Some config -> + Some fundConfig -> require "sender matches the beneficiary defined in AmuletConfig/DevelopmentFundConfig" $ - sender == config.beneficiary + sender == fundConfig.beneficiary coupon <- fetchAndArchive forDso couponCid return TransferInputsSummary with totalAmuletAmount = s.totalAmuletAmount From 7cea94fcffaee25e9b3fbf539f7201ac79241adb Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 09:46:22 +0000 Subject: [PATCH 11/34] Update daml/splice-amulet/daml/Splice/AmuletConfig.daml Co-authored-by: Simon Meier Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/AmuletConfig.daml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daml/splice-amulet/daml/Splice/AmuletConfig.daml b/daml/splice-amulet/daml/Splice/AmuletConfig.daml index cefd2f516f..bb85a3c992 100644 --- a/daml/splice-amulet/daml/Splice/AmuletConfig.daml +++ b/daml/splice-amulet/daml/Splice/AmuletConfig.daml @@ -45,7 +45,8 @@ data AmuletConfig unit = AmuletConfig with -- that should be used for command submissions. transferPreapprovalFee : Optional Decimal -- ^ Fee for keeping a transfer pre-approval around. featuredAppActivityMarkerAmount : Optional Decimal -- ^ $-amount used for the conversion from FeaturedAppActivityMarker -> AppRewardCoupon - developmentFundConfig : Optional DevelopmentFundConfig -- ^ Configuration defining the beneficiary and allocation rate for the Development Fund. + developmentFundConfig : Optional DevelopmentFundConfig + -- ^ Configuration for the development fund mechanics from CIP-0082. deriving (Eq, Show) -- $1/year specified as a daily rate From 6d53cffe9eb1135e2d12510f481951f5b1ef8bf5 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 10:22:05 +0000 Subject: [PATCH 12/34] Update daml/splice-amulet/daml/Splice/AmuletRules.daml Co-authored-by: Simon Meier Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/AmuletRules.daml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daml/splice-amulet/daml/Splice/AmuletRules.daml b/daml/splice-amulet/daml/Splice/AmuletRules.daml index 938c0fd5b3..4b1138a8a5 100644 --- a/daml/splice-amulet/daml/Splice/AmuletRules.daml +++ b/daml/splice-amulet/daml/Splice/AmuletRules.daml @@ -1013,7 +1013,7 @@ summarizeAndConsumeInputs csum dso sender inps optFundConfig = do case optFundConfig of None -> abort "DevelopmentFundCoupon cannot be consumed without AmuletConfig/DevelopmentFundConfig configured" Some fundConfig -> - require "sender matches the beneficiary defined in AmuletConfig/DevelopmentFundConfig" $ + require "sender matches development fund beneficiary" $ sender == fundConfig.beneficiary coupon <- fetchAndArchive forDso couponCid return TransferInputsSummary with From c123209cdd7136194699fe1dd5ecb0f03c5eab57 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 10:41:04 +0000 Subject: [PATCH 13/34] Update daml/splice-amulet/daml/Splice/Issuance.daml Co-authored-by: Simon Meier Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/Issuance.daml | 1 - 1 file changed, 1 deletion(-) diff --git a/daml/splice-amulet/daml/Splice/Issuance.daml b/daml/splice-amulet/daml/Splice/Issuance.daml index 0ca451d4d1..b2cd361e1e 100644 --- a/daml/splice-amulet/daml/Splice/Issuance.daml +++ b/daml/splice-amulet/daml/Splice/Issuance.daml @@ -100,7 +100,6 @@ computeIssuingRoundParameters tickDuration amuletPrice config summary optDevelop amuletsToIssueToDevelopmentFund where developmentFundPercentage = fromOptional 0.0 optDevelopmentFundPercentage - remainingEmissionPercentage = 1.0 - developmentFundPercentage amuletsToIssueToSvs = amuletsToIssueInRound * remainingEmissionPercentage From 1a2f55c60cb57ed61bd1850a45059dde376a3a60 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 10:41:28 +0000 Subject: [PATCH 14/34] Update daml/splice-amulet/daml/Splice/Issuance.daml Co-authored-by: Simon Meier Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/Issuance.daml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daml/splice-amulet/daml/Splice/Issuance.daml b/daml/splice-amulet/daml/Splice/Issuance.daml index b2cd361e1e..cf2dd84a1a 100644 --- a/daml/splice-amulet/daml/Splice/Issuance.daml +++ b/daml/splice-amulet/daml/Splice/Issuance.daml @@ -122,7 +122,7 @@ computeIssuingRoundParameters tickDuration amuletPrice config summary optDevelop intToDecimal (convertRelTimeToMicroseconds tickDuration) amuletsToIssueInRound = config.amuletToIssuePerYear / roundsPerYear - adjustedAmuletsToIssueInRound = amuletsToIssueInRound * remainingEmissionPercentage + adjustedAmuletsToIssueInRound = amuletsToIssueInRound - amuletsToIssueToDevelopmentFund amuletsToIssueToDevelopmentFund = amuletsToIssueInRound * developmentFundPercentage validatorRewardIssuance = computeIssuanceTranche From a7fd0f0581fd824b23214272676544bfa6f27774 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 10:42:55 +0000 Subject: [PATCH 15/34] Update daml/splice-amulet/daml/Splice/Issuance.daml Co-authored-by: Simon Meier Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/Issuance.daml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daml/splice-amulet/daml/Splice/Issuance.daml b/daml/splice-amulet/daml/Splice/Issuance.daml index cf2dd84a1a..8c4c2f68a5 100644 --- a/daml/splice-amulet/daml/Splice/Issuance.daml +++ b/daml/splice-amulet/daml/Splice/Issuance.daml @@ -102,7 +102,7 @@ computeIssuingRoundParameters tickDuration amuletPrice config summary optDevelop developmentFundPercentage = fromOptional 0.0 optDevelopmentFundPercentage amuletsToIssueToSvs = - amuletsToIssueInRound * remainingEmissionPercentage + adjustedAmuletsToIssueInRound - validatorRewardIssuance.rewardsToIssue - unfeaturedAppIssuance.rewardsToIssue From 0cde44e734ac1b3712feff3ffb6ff840e7986364 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 10:44:31 +0000 Subject: [PATCH 16/34] Update daml/splice-dso-governance/daml/Splice/DsoRules.daml Co-authored-by: Simon Meier Signed-off-by: Jose Velasco - IEU --- daml/splice-dso-governance/daml/Splice/DsoRules.daml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/daml/splice-dso-governance/daml/Splice/DsoRules.daml b/daml/splice-dso-governance/daml/Splice/DsoRules.daml index 5bbccead65..eff89fb809 100644 --- a/daml/splice-dso-governance/daml/Splice/DsoRules.daml +++ b/daml/splice-dso-governance/daml/Splice/DsoRules.daml @@ -1324,8 +1324,7 @@ template DsoRules with -- Batch merge of of development fund coupons nonconsuming choice DsoRules_MergeDevelopmentFundCoupons : DsoRules_MergeDevelopmentFundCouponsResult with - amuletRulesCid : ContractId AmuletRules - developmentFundCouponCids : [ContractId DevelopmentFundCoupon] + choiceArg : AmuletRules_MergeDevelopmentFundCoupons sv : Optional Party controller sv do From 38e2d1efedf270fe14236701a1c5e7dc61c1e20a Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 10:44:58 +0000 Subject: [PATCH 17/34] Update daml/splice-dso-governance/daml/Splice/DsoRules.daml Co-authored-by: Simon Meier Signed-off-by: Jose Velasco - IEU --- daml/splice-dso-governance/daml/Splice/DsoRules.daml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daml/splice-dso-governance/daml/Splice/DsoRules.daml b/daml/splice-dso-governance/daml/Splice/DsoRules.daml index eff89fb809..af5a9384d4 100644 --- a/daml/splice-dso-governance/daml/Splice/DsoRules.daml +++ b/daml/splice-dso-governance/daml/Splice/DsoRules.daml @@ -1329,7 +1329,7 @@ template DsoRules with controller sv do _ <- getAndValidateSvParty this sv - result <- exercise amuletRulesCid AmuletRules_MergeDevelopmentFundCoupons with .. + result <- exercise amuletRulesCid choiceArg return DsoRules_MergeDevelopmentFundCouponsResult with developmentFundCoupon = result.developmentFundCouponCid From dcf118ec9a1837a319cf34f743eb2b56b0949ccd Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 10:47:42 +0000 Subject: [PATCH 18/34] Update daml/splice-dso-governance/daml/Splice/DsoRules.daml Co-authored-by: Simon Meier Signed-off-by: Jose Velasco - IEU --- daml/splice-dso-governance/daml/Splice/DsoRules.daml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daml/splice-dso-governance/daml/Splice/DsoRules.daml b/daml/splice-dso-governance/daml/Splice/DsoRules.daml index af5a9384d4..eaced3c89c 100644 --- a/daml/splice-dso-governance/daml/Splice/DsoRules.daml +++ b/daml/splice-dso-governance/daml/Splice/DsoRules.daml @@ -254,7 +254,7 @@ data DsoRules_MergeUnclaimedRewardsResult = DsoRules_MergeUnclaimedRewardsResult unclaimedReward: ContractId UnclaimedReward data DsoRules_MergeDevelopmentFundCouponsResult = DsoRules_MergeDevelopmentFundCouponsResult with - developmentFundCoupon : ContractId DevelopmentFundCoupon + result : AmuletRules_MergeDevelopmentFundCouponResult data DsoRules_MiningRound_CloseResult = DsoRules_MiningRound_CloseResult with closedRound : ContractId ClosedMiningRound From d9998bb7128a6cb4681a8ad92975e1fd3a0a6044 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 10:50:06 +0000 Subject: [PATCH 19/34] Update daml/splice-amulet/daml/Splice/Amulet.daml Co-authored-by: Simon Meier Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/Amulet.daml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daml/splice-amulet/daml/Splice/Amulet.daml b/daml/splice-amulet/daml/Splice/Amulet.daml index b8b2281450..e4e667b514 100644 --- a/daml/splice-amulet/daml/Splice/Amulet.daml +++ b/daml/splice-amulet/daml/Splice/Amulet.daml @@ -383,7 +383,7 @@ template SvRewardCoupon with do return SvRewardCoupon_ArchiveAsBeneficiaryResult --- | A coupon for the Foundation’s share of mint emissions under the Development Fund. +-- | A coupon recording an emission for the Development Fund from CIP-0082. template DevelopmentFundCoupon with dso : Party From 7792e408d799e872dafa2f569b3d26ebf40d3406 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 11:09:42 +0000 Subject: [PATCH 20/34] cip-82: fix daml build (DsoRules_MergeDevelopmentFundCoupons) Signed-off-by: Jose Velasco - IEU --- daml/splice-dso-governance/daml/Splice/DsoRules.daml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daml/splice-dso-governance/daml/Splice/DsoRules.daml b/daml/splice-dso-governance/daml/Splice/DsoRules.daml index eaced3c89c..cef20004c7 100644 --- a/daml/splice-dso-governance/daml/Splice/DsoRules.daml +++ b/daml/splice-dso-governance/daml/Splice/DsoRules.daml @@ -254,7 +254,7 @@ data DsoRules_MergeUnclaimedRewardsResult = DsoRules_MergeUnclaimedRewardsResult unclaimedReward: ContractId UnclaimedReward data DsoRules_MergeDevelopmentFundCouponsResult = DsoRules_MergeDevelopmentFundCouponsResult with - result : AmuletRules_MergeDevelopmentFundCouponResult + result : AmuletRules_MergeDevelopmentFundCouponsResult data DsoRules_MiningRound_CloseResult = DsoRules_MiningRound_CloseResult with closedRound : ContractId ClosedMiningRound @@ -1324,14 +1324,14 @@ template DsoRules with -- Batch merge of of development fund coupons nonconsuming choice DsoRules_MergeDevelopmentFundCoupons : DsoRules_MergeDevelopmentFundCouponsResult with + amuletRulesCid : ContractId AmuletRules choiceArg : AmuletRules_MergeDevelopmentFundCoupons sv : Optional Party controller sv do _ <- getAndValidateSvParty this sv result <- exercise amuletRulesCid choiceArg - return DsoRules_MergeDevelopmentFundCouponsResult with - developmentFundCoupon = result.developmentFundCouponCid + return DsoRules_MergeDevelopmentFundCouponsResult with result nonconsuming choice DsoRules_MiningRound_Close : DsoRules_MiningRound_CloseResult with From a5c8c67acfb4af63b436b61b3c46e4cbecf4c7ea Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 11:58:02 +0000 Subject: [PATCH 21/34] cip-82: refine documentation to remove references to config fields Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/AmuletConfig.daml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/daml/splice-amulet/daml/Splice/AmuletConfig.daml b/daml/splice-amulet/daml/Splice/AmuletConfig.daml index bb85a3c992..7815a14d6b 100644 --- a/daml/splice-amulet/daml/Splice/AmuletConfig.daml +++ b/daml/splice-amulet/daml/Splice/AmuletConfig.daml @@ -45,7 +45,7 @@ data AmuletConfig unit = AmuletConfig with -- that should be used for command submissions. transferPreapprovalFee : Optional Decimal -- ^ Fee for keeping a transfer pre-approval around. featuredAppActivityMarkerAmount : Optional Decimal -- ^ $-amount used for the conversion from FeaturedAppActivityMarker -> AppRewardCoupon - developmentFundConfig : Optional DevelopmentFundConfig + developmentFundConfig : Optional DevelopmentFundConfig -- ^ Configuration for the development fund mechanics from CIP-0082. deriving (Eq, Show) @@ -115,8 +115,7 @@ data PackageConfig = PackageConfig validPackageConfig : PackageConfig -> Bool validPackageConfig _ = True --- | The development fund config defines the beneficiary party and --- the percentage of each mint emission allocated to the Foundation-governed fund. +-- | Configuration for the Development Fund established under CIP-0082. -- -- See Splice.Scripts.Parameters for concrete values. data DevelopmentFundConfig = DevelopmentFundConfig From f5d9b435e833d48a2b2747f2ddc3d7529ef153a4 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 12:23:43 +0000 Subject: [PATCH 22/34] cip-82: make beneficiary optional to decouple fund issuance activation from collection and fund management setup Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/AmuletConfig.daml | 2 +- daml/splice-amulet/daml/Splice/AmuletRules.daml | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/daml/splice-amulet/daml/Splice/AmuletConfig.daml b/daml/splice-amulet/daml/Splice/AmuletConfig.daml index 7815a14d6b..526b1196eb 100644 --- a/daml/splice-amulet/daml/Splice/AmuletConfig.daml +++ b/daml/splice-amulet/daml/Splice/AmuletConfig.daml @@ -120,7 +120,7 @@ validPackageConfig _ = True -- See Splice.Scripts.Parameters for concrete values. data DevelopmentFundConfig = DevelopmentFundConfig with - beneficiary : Party + beneficiary : Optional Party percentage : Decimal deriving (Show, Eq) diff --git a/daml/splice-amulet/daml/Splice/AmuletRules.daml b/daml/splice-amulet/daml/Splice/AmuletRules.daml index 4b1138a8a5..dfa8917f23 100644 --- a/daml/splice-amulet/daml/Splice/AmuletRules.daml +++ b/daml/splice-amulet/daml/Splice/AmuletRules.daml @@ -1010,11 +1010,13 @@ summarizeAndConsumeInputs csum dso sender inps optFundConfig = do totalDevelopmentFundAmount = s.totalDevelopmentFundAmount summarizeAndConsumeInput _round s (InputDevelopmentFundCoupon couponCid) = do + let abortWithBeneficiaryNotConfigured = + abort "DevelopmentFundCoupon cannot be consumed without a beneficiary in the development fund configuration" case optFundConfig of - None -> abort "DevelopmentFundCoupon cannot be consumed without AmuletConfig/DevelopmentFundConfig configured" - Some fundConfig -> - require "sender matches development fund beneficiary" $ - sender == fundConfig.beneficiary + None -> abortWithBeneficiaryNotConfigured + Some (DevelopmentFundConfig { beneficiary = None }) -> abortWithBeneficiaryNotConfigured + Some (DevelopmentFundConfig { beneficiary = Some beneficiary }) -> + require "sender matches development fund beneficiary" $ sender == beneficiary coupon <- fetchAndArchive forDso couponCid return TransferInputsSummary with totalAmuletAmount = s.totalAmuletAmount From d3561bef8cf23b1af10652b79f25af94f2a4b8ba Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 6 Nov 2025 15:02:37 +0000 Subject: [PATCH 23/34] cip-82: replace DevelomentFundConfig with IssuanceConfig.optDevelopmentFundPercentage and TransferConfig.optDevelopmentFundBeneficiary Signed-off-by: Jose Velasco - IEU --- .../Scripts/TestLockAndAmuletExpiry.daml | 1 - .../Splice/Scripts/UnitTests/Issuance.daml | 16 +++--- .../daml/Splice/AmuletConfig.daml | 29 ++-------- .../daml/Splice/AmuletRules.daml | 57 +++++++------------ daml/splice-amulet/daml/Splice/Issuance.daml | 14 +++-- .../daml/Splice/Scripts/TestGovernance.daml | 1 + .../Testing/Registries/AmuletRegistry.daml | 1 + .../Registries/AmuletRegistry/Parameters.daml | 6 +- 8 files changed, 47 insertions(+), 78 deletions(-) diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml b/daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml index e862cecab0..f39dae024a 100644 --- a/daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml +++ b/daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml @@ -33,7 +33,6 @@ scaleAmuletConfig amuletPrice config = AmuletConfig with packageConfig = config.packageConfig transferPreapprovalFee = fmap (/ amuletPrice) config.transferPreapprovalFee featuredAppActivityMarkerAmount = fmap (/ amuletPrice) config.featuredAppActivityMarkerAmount - developmentFundConfig = config.developmentFundConfig test : Script () test = script do diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml b/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml index 8ec80eef07..ec2bd62bb0 100644 --- a/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml +++ b/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml @@ -120,11 +120,11 @@ testE1 : Script () testE1 = script do validateOpenMiningRoundSummary summaryExample1 - expectedParameters_E1_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExample1 None - expectedParameters_E1_0p5_1p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0p5_1p5 summaryExample1 None - expectedParameters_E1_1p5_5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_1p5_5 summaryExample1 None - expectedParameters_E1_5_10 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_5_10 summaryExample1 None - expectedParameters_E1_10plus === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExample1 None + expectedParameters_E1_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExample1 + expectedParameters_E1_0p5_1p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0p5_1p5 summaryExample1 + expectedParameters_E1_1p5_5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_1p5_5 summaryExample1 + expectedParameters_E1_5_10 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_5_10 summaryExample1 + expectedParameters_E1_10plus === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExample1 -- Example 2: all zeros @@ -154,7 +154,7 @@ testNoActivity : Script () testNoActivity = script do validateOpenMiningRoundSummary summaryExamplNoActivity - expectedParameters_NoActivity_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExamplNoActivity None + expectedParameters_NoActivity_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExamplNoActivity -- Example 3: low activity @@ -185,7 +185,7 @@ testE4 : Script () testE4 = script do validateOpenMiningRoundSummary summaryExample3 - expectedParameters_E3_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExample3 None + expectedParameters_E3_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExample3 -- Example 4: millions of SV reward weight @@ -216,7 +216,7 @@ testLargeSvRewardWeight : Script () testLargeSvRewardWeight = script do validateOpenMiningRoundSummary summaryExamplNoActivity - let actual = computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExampleLargeSvRewardWeight None + let actual = computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExampleLargeSvRewardWeight expectedParameters_LargeSvRewardWeight_10plus === actual diff --git a/daml/splice-amulet/daml/Splice/AmuletConfig.daml b/daml/splice-amulet/daml/Splice/AmuletConfig.daml index 526b1196eb..f4cf9f6383 100644 --- a/daml/splice-amulet/daml/Splice/AmuletConfig.daml +++ b/daml/splice-amulet/daml/Splice/AmuletConfig.daml @@ -29,6 +29,7 @@ data TransferConfig unit = TransferConfig with maxNumInputs : Int -- ^ Maximum number of batch inputs for a transfer. maxNumOutputs : Int -- ^ Maximum number of batch outputs for a transfer. maxNumLockHolders : Int -- ^ Maximum number of lock holders allowed for a locked amulet. + optDevelopmentFundBeneficiary : Optional Party -- ^ Beneficiary for the Development Fund under CIP-0082. deriving (Eq, Show) -- | Configuration includes TransferConfig, issuance curve and tickDuration @@ -45,8 +46,6 @@ data AmuletConfig unit = AmuletConfig with -- that should be used for command submissions. transferPreapprovalFee : Optional Decimal -- ^ Fee for keeping a transfer pre-approval around. featuredAppActivityMarkerAmount : Optional Decimal -- ^ $-amount used for the conversion from FeaturedAppActivityMarker -> AppRewardCoupon - developmentFundConfig : Optional DevelopmentFundConfig - -- ^ Configuration for the development fund mechanics from CIP-0082. deriving (Eq, Show) -- $1/year specified as a daily rate @@ -63,8 +62,7 @@ validAmuletConfig AmuletConfig , tickDuration , packageConfig , transferPreapprovalFee - , featuredAppActivityMarkerAmount - , developmentFundConfig } + , featuredAppActivityMarkerAmount} = validTransferConfig transferConfig && validIssuanceCurve issuanceCurve && @@ -72,8 +70,7 @@ validAmuletConfig AmuletConfig tickDuration > days 0 && validPackageConfig packageConfig && optional True (>= 0.0) transferPreapprovalFee && - optional True (>= 0.0) featuredAppActivityMarkerAmount && - optional True validDevelopmentFundConfig developmentFundConfig + optional True (>= 0.0) featuredAppActivityMarkerAmount validTransferConfig : TransferConfig unit -> Bool validTransferConfig TransferConfig @@ -115,19 +112,6 @@ data PackageConfig = PackageConfig validPackageConfig : PackageConfig -> Bool validPackageConfig _ = True --- | Configuration for the Development Fund established under CIP-0082. --- --- See Splice.Scripts.Parameters for concrete values. -data DevelopmentFundConfig = DevelopmentFundConfig - with - beneficiary : Optional Party - percentage : Decimal - deriving (Show, Eq) - -validDevelopmentFundConfig : DevelopmentFundConfig -> Bool -validDevelopmentFundConfig DevelopmentFundConfig { percentage } = - percentage >= 0.0 && percentage <= 1.0 - instance Patchable (AmuletConfig USD) where patch new base current = AmuletConfig with transferConfig = patch new.transferConfig base.transferConfig current.transferConfig @@ -137,7 +121,6 @@ instance Patchable (AmuletConfig USD) where packageConfig = patch new.packageConfig base.packageConfig current.packageConfig transferPreapprovalFee = patch new.transferPreapprovalFee base.transferPreapprovalFee current.transferPreapprovalFee featuredAppActivityMarkerAmount = patch new.featuredAppActivityMarkerAmount base.featuredAppActivityMarkerAmount current.featuredAppActivityMarkerAmount - developmentFundConfig = patch new.developmentFundConfig base.developmentFundConfig current.developmentFundConfig instance Patchable (TransferConfig USD) where patch new base current = TransferConfig with @@ -149,6 +132,7 @@ instance Patchable (TransferConfig USD) where maxNumInputs = patch new.maxNumInputs base.maxNumInputs current.maxNumInputs maxNumOutputs = patch new.maxNumOutputs base.maxNumOutputs current.maxNumOutputs maxNumLockHolders = patch new.maxNumLockHolders base.maxNumLockHolders current.maxNumLockHolders + optDevelopmentFundBeneficiary = patch new.optDevelopmentFundBeneficiary base.optDevelopmentFundBeneficiary current.optDevelopmentFundBeneficiary instance Patchable PackageConfig where patch new base current = PackageConfig with @@ -158,8 +142,3 @@ instance Patchable PackageConfig where validatorLifecycle = patch new.validatorLifecycle base.validatorLifecycle current.validatorLifecycle wallet = patch new.wallet base.wallet current.wallet walletPayments = patch new.walletPayments base.walletPayments current.walletPayments - -instance Patchable DevelopmentFundConfig where - patch new base current = DevelopmentFundConfig with - beneficiary = patch new.beneficiary base.beneficiary current.beneficiary - percentage = patch new.percentage base.percentage current.percentage diff --git a/daml/splice-amulet/daml/Splice/AmuletRules.daml b/daml/splice-amulet/daml/Splice/AmuletRules.daml index dfa8917f23..8398e2dc35 100644 --- a/daml/splice-amulet/daml/Splice/AmuletRules.daml +++ b/daml/splice-amulet/daml/Splice/AmuletRules.daml @@ -25,7 +25,7 @@ import Splice.Api.Token.MetadataV1 as Api.Token.MetadataV1 import Splice.Api.Token.HoldingV1 qualified as Api.Token.HoldingV1 import Splice.Amulet import Splice.Amulet.TokenApiUtils -import Splice.AmuletConfig (AmuletConfig(..), DevelopmentFundConfig(..), TransferConfig(..), validAmuletConfig, defaultTransferPreapprovalFee) +import Splice.AmuletConfig (AmuletConfig(..), TransferConfig(..), validAmuletConfig, defaultTransferPreapprovalFee) import qualified Splice.AmuletConfig as Unit import Splice.Schedule import Splice.Expiry @@ -132,13 +132,11 @@ template AmuletRules -- one under their control. controller Set.toList (transferControllers transfer) do - now <- getTime checkExpectedDso dso expectedDso let rewardsConfig = RewardsIssuanceConfig with issueAppRewards = True issueValidatorRewards = True - configUsd = getValueAsOf now configSchedule - executeTransfer rewardsConfig context dso configUsd.developmentFundConfig transfer + executeTransfer rewardsConfig context dso transfer nonconsuming choice AmuletRules_CreateExternalPartySetupProposal : AmuletRules_CreateExternalPartySetupProposalResult -- ^ Propose to host an external party @@ -161,8 +159,7 @@ template AmuletRules require "preapprovalExpiresAt is not in the past" (preapprovalExpiresAt > now) let configUsd = getValueAsOf now configSchedule (amuletPaid, _) <- computeTransferPreapprovalFee (preapprovalExpiresAt `subTime` now) configUsd context.context dso - (transferResult, meta) <- - splitAndBurn validator amuletPaid inputs context.context dso "create ExternalPartySetupProposal" configUsd.developmentFundConfig + (transferResult, meta) <- splitAndBurn validator amuletPaid inputs context.context dso "create ExternalPartySetupProposal" proposalCid <- create ExternalPartySetupProposal with user validator @@ -190,8 +187,7 @@ template AmuletRules require "expiresAt is not in the past" (expiresAt > now) let configUsd = getValueAsOf now configSchedule (amuletPaid, _) <- computeTransferPreapprovalFee (expiresAt `subTime` now) configUsd context.context dso - (transferResult, meta) <- - splitAndBurn provider amuletPaid inputs context.context dso "create TransferPreapproval" configUsd.developmentFundConfig + (transferResult, meta) <- splitAndBurn provider amuletPaid inputs context.context dso "create TransferPreapproval" transferPreapprovalCid <- create TransferPreapproval with receiver provider @@ -230,8 +226,7 @@ template AmuletRules let transferConfigAmulet = scaleFees (1.0 / openRound.amuletPrice) openRound.transferConfigUsd (trafficCostAmulet, trafficCostUsd) <- computeSynchronizerFees dso provider trafficAmount this context -- We want to burn exactly trafficCostAmulet, and createFee is already burnt by the self-transfer itself, so we subtract the createFee from the amount we split off and burn explicitly. - (transferResult, meta) <- - splitAndBurn provider (trafficCostAmulet - transferConfigAmulet.createFee.fee) inputs context dso "traffic purchase" configUsd.developmentFundConfig + (transferResult, meta) <- splitAndBurn provider (trafficCostAmulet - transferConfigAmulet.createFee.fee) inputs context dso "traffic purchase" -- create a new MemberTraffic contract for the purchased traffic purchasedTraffic <- create MemberTraffic with dso @@ -446,14 +441,11 @@ template AmuletRules summary : OpenMiningRoundSummary controller dso do - now <- getTime miningRound <- fetchAndArchive (ForDso with dso) miningRoundCid validateOpenMiningRoundSummary summary - let - optDevelopmentFundPercentage = (.percentage) <$> (getValueAsOf now configSchedule).developmentFundConfig - params = computeIssuingRoundParameters - miningRound.tickDuration miningRound.amuletPrice miningRound.issuanceConfig summary optDevelopmentFundPercentage + let params = computeIssuingRoundParameters + miningRound.tickDuration miningRound.amuletPrice miningRound.issuanceConfig summary -- record unclaimed reward contract let totalUnclaimedRewards = params.unclaimedValidatorRewards + params.unclaimedAppRewards + params.unclaimedSvRewards @@ -472,6 +464,7 @@ template AmuletRules else pure None -- create issuing round + now <- getTime let tickDuration = miningRound.tickDuration let opensAt = addRelTime now tickDuration let targetClosesAt = addRelTime opensAt (tickDuration + tickDuration) @@ -756,13 +749,11 @@ data RewardsIssuanceConfig = RewardsIssuanceConfig with issueValidatorRewards : Bool -- | Execute a transfer. -executeTransfer - : RewardsIssuanceConfig -> TransferContext -> Party -> Optional DevelopmentFundConfig -> Transfer - -> Update TransferResult -executeTransfer config context dso optFundConfig t = do +executeTransfer : RewardsIssuanceConfig -> TransferContext -> Party -> Transfer -> Update TransferResult +executeTransfer config context dso t = do -- compute summaries csum <- summarizeAndValidateContext context dso t - isum <- summarizeAndConsumeInputs csum dso t.sender t.inputs optFundConfig + isum <- summarizeAndConsumeInputs csum dso t.sender t.inputs osum <- preprocessOutputs csum.config t.sender t.outputs summary <- summarizeTransfer t.sender csum.openRound csum.config isum osum -- check that overall transfer constraints are satisfied @@ -877,8 +868,8 @@ getIssuingMiningRound csum round = do Some issuingRound -> pure issuingRound summarizeAndConsumeInputs - : TransferContextSummary -> Party -> Party -> [TransferInput] -> Optional DevelopmentFundConfig -> Update TransferInputsSummary -summarizeAndConsumeInputs csum dso sender inps optFundConfig = do + : TransferContextSummary -> Party -> Party -> [TransferInput] -> Update TransferInputsSummary +summarizeAndConsumeInputs csum dso sender inps = do foldlA (summarizeAndConsumeInput csum.openRound.round) initialSummary inps where forOwner = ForOwner with dso; owner = sender @@ -1010,13 +1001,9 @@ summarizeAndConsumeInputs csum dso sender inps optFundConfig = do totalDevelopmentFundAmount = s.totalDevelopmentFundAmount summarizeAndConsumeInput _round s (InputDevelopmentFundCoupon couponCid) = do - let abortWithBeneficiaryNotConfigured = - abort "DevelopmentFundCoupon cannot be consumed without a beneficiary in the development fund configuration" - case optFundConfig of - None -> abortWithBeneficiaryNotConfigured - Some (DevelopmentFundConfig { beneficiary = None }) -> abortWithBeneficiaryNotConfigured - Some (DevelopmentFundConfig { beneficiary = Some beneficiary }) -> - require "sender matches development fund beneficiary" $ sender == beneficiary + case csum.config.optDevelopmentFundBeneficiary of + None -> abort "DevelopmentFundCoupon cannot be consumed without the Development Fund beneficiary configured" + Some developmentFundBeneficiary -> require "sender matches development fund beneficiary" $ sender == developmentFundBeneficiary coupon <- fetchAndArchive forDso couponCid return TransferInputsSummary with totalAmuletAmount = s.totalAmuletAmount @@ -1210,6 +1197,7 @@ scaleFees s c = TransferConfig with maxNumInputs = c.maxNumInputs maxNumOutputs = c.maxNumOutputs maxNumLockHolders = c.maxNumLockHolders + optDevelopmentFundBeneficiary = c.optDevelopmentFundBeneficiary transferConfigAmuletFromOpenRound : OpenMiningRound -> TransferConfig Amulet transferConfigAmuletFromOpenRound openRound = scaleFees (1.0 / openRound.amuletPrice) openRound.transferConfigUsd @@ -1592,8 +1580,7 @@ template TransferPreapproval amuletRules <- fetchPublicReferenceData (ForDso dso) context.amuletRules (AmuletRules_Fetch dso) let configUsd = getValueAsOf now amuletRules.configSchedule (amuletPaid, _) <- computeTransferPreapprovalFee extension configUsd context.context dso - (transferResult, meta) <- - splitAndBurn provider amuletPaid inputs context.context dso "renew TransferPreapproval" configUsd.developmentFundConfig + (transferResult, meta) <- splitAndBurn provider amuletPaid inputs context.context dso "renew TransferPreapproval" transferPreapprovalCid <- create this with lastRenewedAt = now expiresAt = newExpiresAt @@ -1651,15 +1638,13 @@ computeTransferPreapprovalFee duration amuletConfig context dso = do let feeAmulet = feeUsd / contextMiningRound.amuletPrice pure (feeAmulet, feeUsd) -splitAndBurn - : Party -> Decimal -> [TransferInput] -> TransferContext -> Party -> Text -> Optional DevelopmentFundConfig - -> Update (TransferResult, Metadata) -splitAndBurn sender amount inputs context dso usage optFundConfig = do +splitAndBurn : Party -> Decimal -> [TransferInput] -> TransferContext -> Party -> Text -> Update (TransferResult, Metadata) +splitAndBurn sender amount inputs context dso usage = do -- do not create activity records as part of the transfer. We create a validator activity record for the full burn below. let rewardsConfig = RewardsIssuanceConfig with issueAppRewards = False issueValidatorRewards = False - transferResult <- executeTransfer rewardsConfig context dso optFundConfig Transfer with + transferResult <- executeTransfer rewardsConfig context dso Transfer with sender provider = sender inputs diff --git a/daml/splice-amulet/daml/Splice/Issuance.daml b/daml/splice-amulet/daml/Splice/Issuance.daml index 8c4c2f68a5..2fed84373f 100644 --- a/daml/splice-amulet/daml/Splice/Issuance.daml +++ b/daml/splice-amulet/daml/Splice/Issuance.daml @@ -25,6 +25,8 @@ data IssuanceConfig = IssuanceConfig with optValidatorFaucetCap : Optional Decimal -- ^ Maximal amount in $ for the per-validator issuance of validator faucet coupons; -- Introduced as part of CIP-3. Defaults to 2.85 USD. + optDevelopmentFundPercentage : Optional Decimal + -- ^ Percentage of each mint emission allocated to the Development Fund under CIP-0082. deriving (Eq, Show) -- | Getter with the right default value for the validator faucet cap. @@ -44,7 +46,8 @@ validIssuanceConfig this@IssuanceConfig{..} = && validatorRewardCap >= 0.0 && featuredAppRewardCap >= 0.0 && unfeaturedAppRewardCap >= 0.0 - && getValidatorFaucetCap this >= 0.0 + && getValidatorFaucetCap this >= 0.0 && + optional True (\pct -> pct >= 0.0 && pct <= 1.0) optDevelopmentFundPercentage -- computation of issuance per round @@ -83,10 +86,8 @@ validateOpenMiningRoundSummary summary = do require "totalUnfeaturedAppRewardCoupons >= 0.0" (summary.totalUnfeaturedAppRewardCoupons >= 0.0) require "totalValidatorFaucetCoupons >= 0" (getTotalValidatorFaucetCoupons summary >= 0) -computeIssuingRoundParameters - : RelTime -> Decimal -> IssuanceConfig -> OpenMiningRoundSummary -> Optional Decimal - -> IssuingRoundParameters -computeIssuingRoundParameters tickDuration amuletPrice config summary optDevelopmentFundPercentage = +computeIssuingRoundParameters : RelTime -> Decimal -> IssuanceConfig -> OpenMiningRoundSummary -> IssuingRoundParameters +computeIssuingRoundParameters tickDuration amuletPrice config summary = IssuingRoundParameters with issuancePerValidatorRewardCoupon = validatorRewardIssuance.issuancePerCoupon issuancePerUnfeaturedAppRewardCoupon = unfeaturedAppIssuance.issuancePerCoupon @@ -99,7 +100,7 @@ computeIssuingRoundParameters tickDuration amuletPrice config summary optDevelop issuancePerValidatorFaucetCoupon = validatorFaucetIssuance.issuancePerCoupon amuletsToIssueToDevelopmentFund where - developmentFundPercentage = fromOptional 0.0 optDevelopmentFundPercentage + developmentFundPercentage = fromOptional 0.0 config.optDevelopmentFundPercentage amuletsToIssueToSvs = adjustedAmuletsToIssueInRound @@ -190,3 +191,4 @@ instance Patchable IssuanceConfig where featuredAppRewardCap = patch new.featuredAppRewardCap base.featuredAppRewardCap current.featuredAppRewardCap unfeaturedAppRewardCap = patch new.unfeaturedAppRewardCap base.unfeaturedAppRewardCap current.unfeaturedAppRewardCap optValidatorFaucetCap = patch new.optValidatorFaucetCap base.optValidatorFaucetCap current.optValidatorFaucetCap + optDevelopmentFundPercentage = patch new.optDevelopmentFundPercentage base.optDevelopmentFundPercentage current.optDevelopmentFundPercentage diff --git a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml index c71be96ff0..d9b8d82b11 100644 --- a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml +++ b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml @@ -746,6 +746,7 @@ testAmuletRulesConfigChange = do maxNumInputs = 101 maxNumOutputs = 101 maxNumLockHolders = 11 + optDevelopmentFundBeneficiary = None let defaultBaseRateTrafficLimits2 = BaseRateTrafficLimits with burstAmount = 10 * 20 * 1000 + 1 diff --git a/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry.daml b/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry.daml index 9018119a6e..b22f670064 100644 --- a/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry.daml +++ b/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry.daml @@ -110,6 +110,7 @@ noTransferFeeConfig = TransferConfig with maxNumInputs = 100 maxNumOutputs = 100 maxNumLockHolders = 10 + optDevelopmentFundBeneficiary = None -- | Initialize the mock amulet registry. initialize : AmuletRegistryConfig -> Script AmuletRegistry diff --git a/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml b/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml index c385616c90..0da7537b24 100644 --- a/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml +++ b/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml @@ -50,6 +50,9 @@ defaultTransferConfig = TransferConfig with -- Chosen conservatively, but high enough to invite thinking about what's possible. maxNumLockHolders = 10 + -- Beneficiary for the Development Fund under CIP-0082. + optDevelopmentFundBeneficiary = None + -- | Decentralized synchronizer config to use for testing. defaultAmuletDecentralizedSynchronizerConfig : AmuletDecentralizedSynchronizerConfig defaultAmuletDecentralizedSynchronizerConfig = AmuletDecentralizedSynchronizerConfig with @@ -95,8 +98,6 @@ defaultAmuletConfig = AmuletConfig with -- Amount of the AppRewardCoupon contract that a FeaturedAppActivityMarker is converted to. featuredAppActivityMarkerAmount = Some defaultFeaturedAppActivityMarkerAmount - developmentFundConfig = None - -- | Default configuration schedule with single current amulet config defaultAmuletConfigSchedule : Schedule Time (AmuletConfig USD) defaultAmuletConfigSchedule = Schedule with @@ -140,6 +141,7 @@ issuanceConfig_10plus = IssuanceConfig with featuredAppRewardCap = 100.0 unfeaturedAppRewardCap = 0.6 optValidatorFaucetCap = None -- We use the default of 2.85 USD introduced in the upgrade for CIP-3 + optDevelopmentFundPercentage = None defaultIssuanceCurve : Schedule RelTime IssuanceConfig From 91dbe44bb62d2bc008c7abf1f585aac45957122a Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Mon, 10 Nov 2025 13:13:26 +0000 Subject: [PATCH 24/34] cip-82: add unit test for issuance with 5% development fund Signed-off-by: Jose Velasco - IEU --- .../Splice/Scripts/UnitTests/Issuance.daml | 144 +++++++++++++++++- 1 file changed, 136 insertions(+), 8 deletions(-) diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml b/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml index ec2bd62bb0..5c391e4665 100644 --- a/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml +++ b/daml/splice-amulet-test/daml/Splice/Scripts/UnitTests/Issuance.daml @@ -21,6 +21,7 @@ tickDuration = defaultAmuletConfig.tickDuration amuletPrice : Decimal amuletPrice = 0.005 + -- Issuance curve retrieval --------------------------- @@ -68,6 +69,15 @@ expectedParameters_E1_0_0p5 = IssuingRoundParameters with unclaimedSvRewards = 0.0000000001 amuletsToIssueToDevelopmentFund = 0.0 +expectedParameters_E1_0_0p5_DevFund5 : IssuingRoundParameters +expectedParameters_E1_0_0p5_DevFund5 = + expectedParameters_E1_0_0p5 with + issuancePerSvRewardCoupon = 5783.8660578387 -- decreased by ~5 % (expected linear scaling with 5 % fund allocation) + unclaimedValidatorRewards = 5649.1628614916 -- decreased by ~25 % (validator tranche near cap: small pool cut sharply reduces unclaimed remainder) + unclaimedAppRewards = 62687.4885844749 -- decreased by ~8.3 % (app tranche less constrained; closer to linear scaling) + unclaimedSvRewards = 0.0 -- decreased ~5 % (below rounding threshold) + amuletsToIssueToDevelopmentFund = 38051.7503805175 + expectedParameters_E1_0p5_1p5 : IssuingRoundParameters expectedParameters_E1_0p5_1p5 = IssuingRoundParameters with issuancePerValidatorRewardCoupon = 0.200000000 @@ -80,6 +90,15 @@ expectedParameters_E1_0p5_1p5 = IssuingRoundParameters with unclaimedSvRewards = 0.000000004 amuletsToIssueToDevelopmentFund = 0.0 +expectedParameters_E1_0p5_1p5_DevFund5 : IssuingRoundParameters +expectedParameters_E1_0p5_1p5_DevFund5 = + expectedParameters_E1_0p5_1p5 with + issuancePerSvRewardCoupon = 1735.1598173516 -- decreased by ~5 % (expected linear scaling with 5 % fund allocation) + unclaimedValidatorRewards = 12878.9954337899 -- decreased by ~15 % (validator tranche near cap: small pool cut sharply reduces unclaimed remainder) + unclaimedAppRewards = 98836.6514459665 -- decreased by ~10 % (app tranche less constrained; closer to linear scaling) + unclaimedSvRewards = 0.0 -- decreased ~5 % (below rounding threshold) + amuletsToIssueToDevelopmentFund = 19025.8751902588 + expectedParameters_E1_1p5_5 : IssuingRoundParameters expectedParameters_E1_1p5_5 = IssuingRoundParameters with issuancePerValidatorRewardCoupon = 0.200000000 @@ -92,6 +111,15 @@ expectedParameters_E1_1p5_5 = IssuingRoundParameters with unclaimedSvRewards = 0.0 amuletsToIssueToDevelopmentFund = 0.0 +expectedParameters_E1_1p5_5_DevFund5 : IssuingRoundParameters +expectedParameters_E1_1p5_5_DevFund5 = + expectedParameters_E1_1p5_5 with + issuancePerSvRewardCoupon = 361.4916286149 -- decreased by ~5 % (expected linear scaling with 5 % fund allocation) + unclaimedValidatorRewards = 2034.2465753425 -- decreased by ~45 % (validator tranche strongly cap-bound; small pool accentuates drop) + unclaimedAppRewards = 66302.4048706240 -- decreased by ~8.2 % (app tranche less constrained; close to linear scaling) + unclaimedSvRewards = 0.0000000016 -- minor rounding drift (below significance threshold) + amuletsToIssueToDevelopmentFund = 9512.9375951294 + expectedParameters_E1_5_10 : IssuingRoundParameters expectedParameters_E1_5_10 = IssuingRoundParameters with issuancePerValidatorRewardCoupon = 0.200000000 @@ -104,6 +132,16 @@ expectedParameters_E1_5_10 = IssuingRoundParameters with unclaimedSvRewards = 0.0 amuletsToIssueToDevelopmentFund = 0.0 +expectedParameters_E1_5_10_DevFund5 : IssuingRoundParameters +expectedParameters_E1_5_10_DevFund5 = + expectedParameters_E1_5_10 with + issuancePerValidatorFaucetCoupon = 339.5662100457 -- decreased by ~5.6 % (slightly above 5 % due to faucet cap effects) + issuancePerSvRewardCoupon = 90.3729071537 -- decreased by ~5 % (expected linear scaling) + unclaimedValidatorRewards = 0.0 -- minor rounding drift (below significance threshold) + unclaimedAppRewards = 16597.3059360731 -- decreased by ~16.5 % (cap constraints amplify reduction) + unclaimedSvRewards = 0.0000000029 -- minor rounding drift (below significance threshold) + amuletsToIssueToDevelopmentFund = 4756.4687975647 + expectedParameters_E1_10plus : IssuingRoundParameters expectedParameters_E1_10plus = IssuingRoundParameters with issuancePerValidatorRewardCoupon = 0.200000000 @@ -116,22 +154,43 @@ expectedParameters_E1_10plus = IssuingRoundParameters with unclaimedSvRewards = 0.0000000023 amuletsToIssueToDevelopmentFund = 0.0 +expectedParameters_E1_10plus_DevFund5 : IssuingRoundParameters +expectedParameters_E1_10plus_DevFund5 = + expectedParameters_E1_10plus with + issuancePerFeaturedAppRewardCoupon = 70.3246004566 -- decreased by ~6 % (slightly above 5 % since both featured and unfeatured tranches are below cap) + issuancePerValidatorFaucetCoupon = 140.7458143075 -- decreased by ~6.3 % (faucet below cap, reduced pool scales slightly more than linearly) + issuancePerSvRewardCoupon = 22.5932267884 -- decreased by ~5 % (expected linear scaling) + unclaimedAppRewards = 0.0000000084 -- decreased ~5 % (below rounding threshold) + unclaimedSvRewards = 0.0000000033 -- minor rounding drift (below significance threshold) + amuletsToIssueToDevelopmentFund = 2378.2343987823 + + + testE1 : Script () testE1 = script do validateOpenMiningRoundSummary summaryExample1 + -- 0% Development Fund expectedParameters_E1_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExample1 expectedParameters_E1_0p5_1p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0p5_1p5 summaryExample1 expectedParameters_E1_1p5_5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_1p5_5 summaryExample1 expectedParameters_E1_5_10 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_5_10 summaryExample1 expectedParameters_E1_10plus === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExample1 + -- 5% Development Fund + -- Most parameters decrease by ~5%; larger deviations occur when issuance caps bind or rounding applies. + expectedParameters_E1_0_0p5_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_0_0p5) summaryExample1 + expectedParameters_E1_0p5_1p5_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_0p5_1p5) summaryExample1 + expectedParameters_E1_1p5_5_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_1p5_5) summaryExample1 + expectedParameters_E1_5_10_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_5_10) summaryExample1 + expectedParameters_E1_10plus_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_10plus) summaryExample1 + -- Example 2: all zeros ----------------------- -summaryExamplNoActivity : OpenMiningRoundSummary -summaryExamplNoActivity = OpenMiningRoundSummary with +summaryExampleNoActivity : OpenMiningRoundSummary +summaryExampleNoActivity = OpenMiningRoundSummary with totalValidatorRewardCoupons = 0.0 totalFeaturedAppRewardCoupons = 0.0 totalUnfeaturedAppRewardCoupons = 0.0 @@ -150,11 +209,26 @@ expectedParameters_NoActivity_0_0p5 = IssuingRoundParameters with unclaimedSvRewards = 608828.0060882801 amuletsToIssueToDevelopmentFund = 0.0 +expectedParameters_NoActivity_0_0p5_DevFund5 : IssuingRoundParameters +expectedParameters_NoActivity_0_0p5_DevFund5 = + expectedParameters_NoActivity_0_0p5 with + issuancePerSvRewardCoupon = 578386.6057838661 -- decreased by ~5 % (expected linear scaling; no coupons active) + unclaimedValidatorRewards = 36149.1628614916 -- decreased by ~5 % (fund allocation directly reduces total issuance) + unclaimedAppRewards = 108447.4885844749 -- decreased by ~5 % (expected linear scaling) + unclaimedSvRewards = 578386.6057838661 -- decreased by ~5 % (identical scaling since all rewards remain unclaimed) + amuletsToIssueToDevelopmentFund = 38051.7503805175 + + testNoActivity : Script () testNoActivity = script do - validateOpenMiningRoundSummary summaryExamplNoActivity + validateOpenMiningRoundSummary summaryExampleNoActivity + + -- 0% Development Fund + expectedParameters_NoActivity_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExampleNoActivity - expectedParameters_NoActivity_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExamplNoActivity + -- 5% Development Fund + -- Most parameters decrease by ~5%; larger deviations occur when issuance caps bind or rounding applies. + expectedParameters_NoActivity_0_0p5_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_0_0p5) summaryExampleNoActivity -- Example 3: low activity @@ -181,17 +255,29 @@ expectedParameters_E3_0_0p5 = IssuingRoundParameters with unclaimedSvRewards = 0.0000000001 amuletsToIssueToDevelopmentFund = 0.0 +expectedParameters_E3_0_0p5_DevFund5 : IssuingRoundParameters +expectedParameters_E3_0_0p5_DevFund5 = + expectedParameters_E3_0_0p5 with + issuancePerSvRewardCoupon = 289193.302891933 -- decreased by ~5 % (expected linear scaling; SV tranche not capped) + unclaimedValidatorRewards = 7609.1628614916 -- decreased by ~20 % (validator tranche near cap, amplifying reduction) + unclaimedAppRewards = 98387.4885844749 -- decreased by ~5.5 % (mostly linear scaling) + amuletsToIssueToDevelopmentFund = 38051.7503805175 + testE4 : Script () testE4 = script do validateOpenMiningRoundSummary summaryExample3 + -- 0% Development Fund expectedParameters_E3_0_0p5 === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5 summaryExample3 + -- 5% Development Fund + -- Most parameters decrease by ~5%; larger deviations occur when issuance caps bind or rounding applies. + expectedParameters_E3_0_0p5_DevFund5 === computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_0_0p5) summaryExample3 + -- Example 4: millions of SV reward weight ------------------------------------------- - summaryExampleLargeSvRewardWeight : OpenMiningRoundSummary summaryExampleLargeSvRewardWeight = OpenMiningRoundSummary with totalValidatorRewardCoupons = 0.0 @@ -212,12 +298,54 @@ expectedParameters_LargeSvRewardWeight_10plus = IssuingRoundParameters with unclaimedSvRewards = 0.0 amuletsToIssueToDevelopmentFund = 0.0 +expectedParameters_LargeSvRewardWeight_10plus_DevFund5 : IssuingRoundParameters +expectedParameters_LargeSvRewardWeight_10plus_DevFund5 = + expectedParameters_LargeSvRewardWeight_10plus with + issuancePerSvRewardCoupon = 0.0009413844 -- decreased by ~5 % (expected linear scaling; SV weight dominates distribution) + unclaimedValidatorRewards = 9037.2907153729 -- decreased by ~5 % (linear with total issuance) + unclaimedAppRewards = 33889.8401826484 -- decreased by ~5 % (expected linear scaling) + unclaimedSvRewards = 0.0001188433 -- minor rounding drift (below significance threshold) + amuletsToIssueToDevelopmentFund = 2378.2343987823 + testLargeSvRewardWeight : Script () testLargeSvRewardWeight = script do - validateOpenMiningRoundSummary summaryExamplNoActivity + validateOpenMiningRoundSummary summaryExampleNoActivity + + -- 0% Development Fund + expectedParameters_LargeSvRewardWeight_10plus === + computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExampleLargeSvRewardWeight + + -- 5% Development Fund + -- Most parameters decrease by ~5%; larger deviations occur when issuance caps bind or rounding applies. + expectedParameters_LargeSvRewardWeight_10plus_DevFund5 === + computeIssuingRoundParameters tickDuration amuletPrice (withDevFund5 issuanceConfig_10plus) summaryExampleLargeSvRewardWeight - let actual = computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_10plus summaryExampleLargeSvRewardWeight - expectedParameters_LargeSvRewardWeight_10plus === actual +-- Example 5: Development Fund receives the full issuance +---------------------------------------------------------- + +expectedParameters_AllIssuanceForFund : IssuingRoundParameters +expectedParameters_AllIssuanceForFund = IssuingRoundParameters with + issuancePerValidatorRewardCoupon = 0.0 + issuancePerFeaturedAppRewardCoupon = 0.0 + issuancePerUnfeaturedAppRewardCoupon = 0.0 + issuancePerValidatorFaucetCoupon = 0.0 + issuancePerSvRewardCoupon = 0.0 + unclaimedAppRewards = 0.0 + unclaimedValidatorRewards = 0.0 + unclaimedSvRewards = 0.0 + amuletsToIssueToDevelopmentFund = 761035.0076103501 + +testAllIssuanceForFund : Script () +testAllIssuanceForFund = script do + validateOpenMiningRoundSummary summaryExample3 + + -- 100% Development Fund + -- All issuance goes to the Development Fund; all reward-related fields are zero. + let issuanceConfig_0_0p5_fundOne = issuanceConfig_0_0p5 with optDevelopmentFundPercentage = Some 1.0 + expectedParameters_AllIssuanceForFund === computeIssuingRoundParameters tickDuration amuletPrice issuanceConfig_0_0p5_fundOne summaryExample1 +-- Utility: apply 5% Development Fund allocation +withDevFund5 : IssuanceConfig -> IssuanceConfig +withDevFund5 config = config with optDevelopmentFundPercentage = Some 0.05 From 9c083e6dab673efbbc71606f43647ad8652cdb33 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Mon, 10 Nov 2025 15:08:07 +0000 Subject: [PATCH 25/34] cip-82: add optDevelopmentFundPercentage value to testAmuletRulesConfigChange test Signed-off-by: Jose Velasco - IEU --- .../daml/Splice/Scripts/TestGovernance.daml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml index d9b8d82b11..f4353f41ae 100644 --- a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml +++ b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml @@ -767,6 +767,8 @@ testAmuletRulesConfigChange = do amuletToIssuePerYear = 40e9 validatorRewardPercentage = 0.05 appRewardPercentage = 0.15 + optDevelopmentFundPercentage = Some 0.05 + let defaultIssuanceCurve2 = Schedule with initialValue = issuanceConfig_0p5_1p5_2 From ba38837c3fbfcb2c1d87b4f4373c411ea0f0d2af Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Mon, 10 Nov 2025 15:18:58 +0000 Subject: [PATCH 26/34] cip-82: add optDevelopmentFundBeneficiary value to testAmuletRulesConfigChange test Signed-off-by: Jose Velasco - IEU --- .../daml/Splice/Scripts/TestGovernance.daml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml index f4353f41ae..d4ca524273 100644 --- a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml +++ b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestGovernance.daml @@ -730,6 +730,7 @@ testAmuletRulesTickDurationChange = do testAmuletRulesConfigChange : Script () testAmuletRulesConfigChange = do (app, dso, (sv1, sv2, sv3, _)) <- initMainNet + let Some devFundBeneficiary = partyFromText "DevFundBeneficiary" [(dsoRulesCid, _)] <- query @DsoRules dso @@ -746,7 +747,7 @@ testAmuletRulesConfigChange = do maxNumInputs = 101 maxNumOutputs = 101 maxNumLockHolders = 11 - optDevelopmentFundBeneficiary = None + optDevelopmentFundBeneficiary = Some devFundBeneficiary let defaultBaseRateTrafficLimits2 = BaseRateTrafficLimits with burstAmount = 10 * 20 * 1000 + 1 From 9d683ebff4f88fd8e79026827ab4549f4e3016e2 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Mon, 10 Nov 2025 17:14:43 +0000 Subject: [PATCH 27/34] cip-82: add initDevNetWithAmuletConfig test helper Signed-off-by: Jose Velasco - IEU --- .../daml/Splice/Scripts/DsoTestUtils.daml | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/daml/splice-dso-governance-test/daml/Splice/Scripts/DsoTestUtils.daml b/daml/splice-dso-governance-test/daml/Splice/Scripts/DsoTestUtils.daml index 17abe747c3..8187ce50ad 100644 --- a/daml/splice-dso-governance-test/daml/Splice/Scripts/DsoTestUtils.daml +++ b/daml/splice-dso-governance-test/daml/Splice/Scripts/DsoTestUtils.daml @@ -9,12 +9,14 @@ import DA.Assert import DA.Foldable (forA_) import DA.List import qualified DA.Map as Map +import DA.Optional (fromOptional) import qualified DA.Set as Set import qualified DA.Text as T import Daml.Script import DA.Time import Splice.Amulet +import Splice.AmuletConfig (AmuletConfig(..), USD) import Splice.AmuletRules import Splice.Issuance import Splice.Round @@ -38,24 +40,26 @@ bpsMultiplier : Int bpsMultiplier = 10000 initMainNet : Script (AmuletApp, Party, (Party, Party, Party, Party)) -initMainNet = initDecentralizedSynchronizer False +initMainNet = initDecentralizedSynchronizer False None initMainNetWithAmuletPrice : Decimal -> Script (AmuletApp, Party, (Party, Party, Party, Party)) -initMainNetWithAmuletPrice = initDecentralizedSynchronizerWithAmuletPrice False 0 +initMainNetWithAmuletPrice amuletPrice = initDecentralizedSynchronizerWithAmuletPrice False 0 amuletPrice None initDevNet : Script (AmuletApp, Party, (Party, Party, Party, Party)) -initDevNet = initDecentralizedSynchronizer True +initDevNet = initDecentralizedSynchronizer True None +initDevNetWithAmuletConfig : AmuletConfig USD -> Script (AmuletApp, Party, (Party, Party, Party, Party)) +initDevNetWithAmuletConfig amuletConfig = initDecentralizedSynchronizer True (Some amuletConfig) -initDecentralizedSynchronizer : Bool -> Script (AmuletApp, Party, (Party, Party, Party, Party)) -initDecentralizedSynchronizer isDevNet = initDecentralizedSynchronizerWithAmuletPrice isDevNet 0 1.0 +initDecentralizedSynchronizer : Bool -> Optional (AmuletConfig USD) -> Script (AmuletApp, Party, (Party, Party, Party, Party)) +initDecentralizedSynchronizer isDevNet optAmuletConfig = initDecentralizedSynchronizerWithAmuletPrice isDevNet 0 1.0 optAmuletConfig initDecentralizedSynchronizerWithNonZeroRound : Bool -> Int -> Script (AmuletApp, Party, (Party, Party, Party, Party)) -initDecentralizedSynchronizerWithNonZeroRound isDevNet initialRound = initDecentralizedSynchronizerWithAmuletPrice isDevNet initialRound 1.0 +initDecentralizedSynchronizerWithNonZeroRound isDevNet initialRound = initDecentralizedSynchronizerWithAmuletPrice isDevNet initialRound 1.0 None -initDecentralizedSynchronizerWithAmuletPrice : Bool -> Int -> Decimal -> Script (AmuletApp, Party, (Party, Party, Party, Party)) -initDecentralizedSynchronizerWithAmuletPrice isDevNet initialRound amuletPrice = do +initDecentralizedSynchronizerWithAmuletPrice : Bool -> Int -> Decimal -> Optional (AmuletConfig USD) -> Script (AmuletApp, Party, (Party, Party, Party, Party)) +initDecentralizedSynchronizerWithAmuletPrice isDevNet initialRound amuletPrice optAmuletConfig = do [sv1, sv2, sv3, sv4] <- forA ["sv1", "sv2", "sv3", "sv4"] allocateParty dso <- allocateParty "dso-party" @@ -82,7 +86,7 @@ initDecentralizedSynchronizerWithAmuletPrice isDevNet initialRound amuletPrice = decentralizedSynchronizer = initialDsoDecentralizedSynchronizerConfig nextScheduledSynchronizerUpgrade = None voteCooldownTime = None -- use default value of 1 minute - let amuletConfig = defaultAmuletConfig + let amuletConfig = fromOptional defaultAmuletConfig optAmuletConfig let ansRulesConfig = defaultAnsRulesConfig now <- getTime From ecad0f335ff903aa27903b383f5ed4ce66a60a6f Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Mon, 10 Nov 2025 19:31:29 +0000 Subject: [PATCH 28/34] cip-82: test merging of Development Fund coupons Signed-off-by: Jose Velasco - IEU --- .../Scripts/TestDecentralizedAutomation.daml | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml index 5d08b8d107..080e4a4b4b 100644 --- a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml +++ b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml @@ -3,6 +3,7 @@ module Splice.Scripts.TestDecentralizedAutomation where +import DA.Action (replicateA_) import DA.Assert import DA.Foldable (forA_) import DA.List() @@ -26,6 +27,7 @@ import Splice.Scripts.DsoTestUtils import Splice.Ans import Splice.Scripts.AnsRulesParameters import Splice.Scripts.TestTransferPreapproval +import Splice.Testing.Registries.AmuletRegistry.Parameters import Splice.Wallet.Subscriptions @@ -53,6 +55,44 @@ testUnclaimedRewardsMerging = do pure () +testDevelopmentFundCouponsMerging : Script () +testDevelopmentFundCouponsMerging = do + let + -- 5% Development Fund + amuletConfig = + defaultAmuletConfig with + issuanceCurve = defaultAmuletConfig.issuanceCurve with + initialValue = defaultAmuletConfig.issuanceCurve.initialValue with + optDevelopmentFundPercentage = Some 0.05 + (app, _, (sv1, _, _, _)) <- initDevNetWithAmuletConfig amuletConfig + + -- Mint 5 development fund coupons + replicateA_ 5 $ runNextIssuanceD app 1.0 + + [(amuletRulesCid, _)] <- query @AmuletRules app.dso + developmentFundCouponCids@(cid1 :: _) <- fmap fst <$> query @DevelopmentFundCoupon app.dso + length developmentFundCouponCids === 5 + + -- Unhappy - Requires more than one development fund coupon contracts + dsoDelegateSubmitsMustFail app $ \cid -> exerciseCmd cid $ + DsoRules_MergeDevelopmentFundCoupons with + amuletRulesCid + choiceArg = AmuletRules_MergeDevelopmentFundCoupons with + developmentFundCouponCids = [cid1] + sv = Some sv1 + + dsoDelegateSubmits app $ \cid -> exerciseCmd cid $ + DsoRules_MergeDevelopmentFundCoupons with + amuletRulesCid + choiceArg = AmuletRules_MergeDevelopmentFundCoupons with + developmentFundCouponCids + sv = Some sv1 + + developmentFundCoupons <- query @DevelopmentFundCoupon app.dso + length developmentFundCoupons === 1 + + pure () + -- Testing confirmations ------------------------ From c7ca25bf60c85dffdb88fbb98686ff234b4c9908 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Tue, 11 Nov 2025 12:36:26 +0000 Subject: [PATCH 29/34] cip-82 - minor fix in comments/error message Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/AmuletRules.daml | 2 +- .../daml/Splice/Scripts/TestDecentralizedAutomation.daml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/daml/splice-amulet/daml/Splice/AmuletRules.daml b/daml/splice-amulet/daml/Splice/AmuletRules.daml index 8398e2dc35..03fe19b960 100644 --- a/daml/splice-amulet/daml/Splice/AmuletRules.daml +++ b/daml/splice-amulet/daml/Splice/AmuletRules.daml @@ -598,7 +598,7 @@ template AmuletRules developmentFundCouponCids : [ContractId DevelopmentFundCoupon] controller dso do - require "More than one development fund coupon contracts" (length developmentFundCouponCids > 1) + require "More than one development fund coupon contract" (length developmentFundCouponCids > 1) -- archive all given coupons archivedAmounts <- forA developmentFundCouponCids $ \developmentFundCouponCid -> do diff --git a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml index 080e4a4b4b..48b87a0550 100644 --- a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml +++ b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml @@ -73,7 +73,7 @@ testDevelopmentFundCouponsMerging = do developmentFundCouponCids@(cid1 :: _) <- fmap fst <$> query @DevelopmentFundCoupon app.dso length developmentFundCouponCids === 5 - -- Unhappy - Requires more than one development fund coupon contracts + -- Unhappy path - requires more than one development fund coupon contract. dsoDelegateSubmitsMustFail app $ \cid -> exerciseCmd cid $ DsoRules_MergeDevelopmentFundCoupons with amuletRulesCid @@ -81,6 +81,7 @@ testDevelopmentFundCouponsMerging = do developmentFundCouponCids = [cid1] sv = Some sv1 + -- Happy path dsoDelegateSubmits app $ \cid -> exerciseCmd cid $ DsoRules_MergeDevelopmentFundCoupons with amuletRulesCid From 3995ea69bed007e610249522363bef1bbae02365 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Tue, 11 Nov 2025 16:19:24 +0000 Subject: [PATCH 30/34] cip-82: add test for InputDevelopmentFundCoupon Signed-off-by: Jose Velasco - IEU --- .../Scripts/TestAmuletRulesTransfer.daml | 56 +++++++++++++++++++ .../daml/Splice/Scripts/Util.daml | 15 ++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml b/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml index 699a782c8c..11e6304433 100644 --- a/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml +++ b/daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml @@ -3,6 +3,7 @@ module Splice.Scripts.TestAmuletRulesTransfer where +import DA.Action (replicateA_) import DA.Assert import DA.Foldable (forA_) import DA.List @@ -577,3 +578,58 @@ testUnclaimedActivityRecordTransferInput = do transferResult.summary.senderChangeAmount === amountToMint - createFee pure () + +testInputDevelopmentFundCoupon : Script () +testInputDevelopmentFundCoupon = do + defaultAppWithUsers@DefaultAppWithUsers{..} <- setupDefaultAppWithUsers + baseConfig <- getAmuletConfig app + -- Replace the default AmuletConfig with a 0.05% allocation for the development fund + -- and Alice as the beneficiary. This configuration applies to the next open round. + setAmuletConfig app baseConfig baseConfig with + issuanceCurve = baseConfig.issuanceCurve with + initialValue = baseConfig.issuanceCurve.initialValue with + optDevelopmentFundPercentage = Some 0.05 + transferConfig = baseConfig.transferConfig with + optDevelopmentFundBeneficiary = Some alice.primaryParty + + -- Run 3 issuances for the already open rounds without the development fund configuration. + -- No development fund coupons are minted. + replicateA_ 3 $ runNextIssuance app + -- Mint one development fund coupon per issuance (two total). + runNextIssuance app + runNextIssuance app + context <- getPaymentTransferContext app alice + [(cid1, coupon1), (cid2, coupon2)] <- query @DevelopmentFundCoupon app.dso + let totalAmount = coupon1.amount + coupon2.amount + + -- Unhappy path - requires that the sender matches the development fund beneficiary. + submitMultiMustFail [bob.primaryParty] [app.dso] $ + exerciseCmd context.amuletRules AmuletRules_Transfer with + transfer = Transfer with + sender = bob.primaryParty + provider = bob.primaryParty + inputs = [InputDevelopmentFundCoupon cid1, InputDevelopmentFundCoupon cid2] + outputs = [] + beneficiaries = None + context = context.context + expectedDso = Some app.dso + + -- Happy path + transferResult <- + checkTransferMetadata app TxKind_MergeSplit alice.primaryParty $ + checkBalanceChanges defaultAppWithUsers $ + submitMulti [alice.primaryParty] [app.dso] $ + exerciseCmd context.amuletRules AmuletRules_Transfer with + transfer = Transfer with + sender = alice.primaryParty + provider = alice.primaryParty + inputs = [InputDevelopmentFundCoupon cid1, InputDevelopmentFundCoupon cid2] + outputs = [] + beneficiaries = None + context = context.context + expectedDso = Some app.dso + (_, openRound) <- getLatestActiveOpenRound app + let createFee = openRound.transferConfigUsd.createFee.fee * openRound.amuletPrice + transferResult.summary.senderChangeAmount === totalAmount - createFee + + pure () diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/Util.daml b/daml/splice-amulet-test/daml/Splice/Scripts/Util.daml index f7b47d47d6..62b498b525 100644 --- a/daml/splice-amulet-test/daml/Splice/Scripts/Util.daml +++ b/daml/splice-amulet-test/daml/Splice/Scripts/Util.daml @@ -646,6 +646,14 @@ getAmuletConfig app = do now <- getTime pure $ getValueAsOf now amuletRules.configSchedule +setAmuletConfig : AmuletApp -> AmuletConfig Unit.USD -> AmuletConfig Unit.USD -> Script () +setAmuletConfig app baseConfig newConfig = do + Some (amuletRulesCid, _) <- queryAmuletRulesByKey app.dso + void $ submit app.dso $ + exerciseCmd amuletRulesCid AmuletRules_SetConfig with + newConfig + baseConfig + -- Metadata verification ------------------------ @@ -693,13 +701,16 @@ genericCheckTxMetadata extractMeta extractSummary app expectedKind sender body = mint <- case extractSummary result of None -> pure 0.0 Some summary -> do - let inputUnclaimedActivityRecordAmount = fromOptional 0.0 summary.inputUnclaimedActivityRecordAmount + let + inputUnclaimedActivityRecordAmount = fromOptional 0.0 summary.inputUnclaimedActivityRecordAmount + inputDevelopmentFundAmountAmount = fromOptional 0.0 summary.inputDevelopmentFundAmount expectUnlessZero svRewardAmountMetaKey summary.inputSvRewardAmount expectUnlessZero appRewardAmountMetaKey summary.inputAppRewardAmount expectUnlessZero validatorRewardAmountMetaKey summary.inputValidatorRewardAmount expectUnlessZero unclaimedActivityRecordAmountMetaKey inputUnclaimedActivityRecordAmount + expectUnlessZero developmentFundAmountMetaKey inputDevelopmentFundAmountAmount pure $ summary.inputAppRewardAmount + summary.inputValidatorRewardAmount + summary.inputSvRewardAmount + - inputUnclaimedActivityRecordAmount + inputUnclaimedActivityRecordAmount + inputDevelopmentFundAmountAmount let expectedBurn = totalHoldingsBefore + mint - totalHoldingsAfter -- mints are inferred, and show here as a negative burn if (expectedBurn < 0.0) From 86c302817a65e7a34e8d24b78388c2844bab82e3 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 13 Nov 2025 09:35:51 +0000 Subject: [PATCH 31/34] Update daml/splice-amulet/daml/Splice/Issuance.daml Co-authored-by: Simon Meier Signed-off-by: Jose Velasco - IEU --- daml/splice-amulet/daml/Splice/Issuance.daml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daml/splice-amulet/daml/Splice/Issuance.daml b/daml/splice-amulet/daml/Splice/Issuance.daml index 2fed84373f..d53ac5ff7b 100644 --- a/daml/splice-amulet/daml/Splice/Issuance.daml +++ b/daml/splice-amulet/daml/Splice/Issuance.daml @@ -46,8 +46,8 @@ validIssuanceConfig this@IssuanceConfig{..} = && validatorRewardCap >= 0.0 && featuredAppRewardCap >= 0.0 && unfeaturedAppRewardCap >= 0.0 - && getValidatorFaucetCap this >= 0.0 && - optional True (\pct -> pct >= 0.0 && pct <= 1.0) optDevelopmentFundPercentage + && getValidatorFaucetCap this >= 0.0 + && optional True (\pct -> pct >= 0.0 && pct <= 1.0) optDevelopmentFundPercentage -- computation of issuance per round From c851d5e9b0315bd1fe5b5f11ca785167a2d42caf Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 13 Nov 2025 09:38:26 +0000 Subject: [PATCH 32/34] Update daml/splice-dso-governance/daml/Splice/DsoRules.daml Co-authored-by: Simon Meier Signed-off-by: Jose Velasco - IEU --- daml/splice-dso-governance/daml/Splice/DsoRules.daml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daml/splice-dso-governance/daml/Splice/DsoRules.daml b/daml/splice-dso-governance/daml/Splice/DsoRules.daml index cef20004c7..a9c3eb551f 100644 --- a/daml/splice-dso-governance/daml/Splice/DsoRules.daml +++ b/daml/splice-dso-governance/daml/Splice/DsoRules.daml @@ -1326,7 +1326,7 @@ template DsoRules with with amuletRulesCid : ContractId AmuletRules choiceArg : AmuletRules_MergeDevelopmentFundCoupons - sv : Optional Party + sv : Party controller sv do _ <- getAndValidateSvParty this sv From a40f4c85d29bcdbfb724403b33e1c88aaffdf8d6 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 13 Nov 2025 09:49:58 +0000 Subject: [PATCH 33/34] cip-82: fix daml build Signed-off-by: Jose Velasco - IEU --- .../daml/Splice/Scripts/TestDecentralizedAutomation.daml | 4 ++-- daml/splice-dso-governance/daml/Splice/DsoRules.daml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml index 48b87a0550..ce6ecfb2cf 100644 --- a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml +++ b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestDecentralizedAutomation.daml @@ -79,7 +79,7 @@ testDevelopmentFundCouponsMerging = do amuletRulesCid choiceArg = AmuletRules_MergeDevelopmentFundCoupons with developmentFundCouponCids = [cid1] - sv = Some sv1 + sv = sv1 -- Happy path dsoDelegateSubmits app $ \cid -> exerciseCmd cid $ @@ -87,7 +87,7 @@ testDevelopmentFundCouponsMerging = do amuletRulesCid choiceArg = AmuletRules_MergeDevelopmentFundCoupons with developmentFundCouponCids - sv = Some sv1 + sv = sv1 developmentFundCoupons <- query @DevelopmentFundCoupon app.dso length developmentFundCoupons === 1 diff --git a/daml/splice-dso-governance/daml/Splice/DsoRules.daml b/daml/splice-dso-governance/daml/Splice/DsoRules.daml index a9c3eb551f..47f02a1245 100644 --- a/daml/splice-dso-governance/daml/Splice/DsoRules.daml +++ b/daml/splice-dso-governance/daml/Splice/DsoRules.daml @@ -1329,7 +1329,7 @@ template DsoRules with sv : Party controller sv do - _ <- getAndValidateSvParty this sv + _ <- getAndValidateSvParty this (Some sv) result <- exercise amuletRulesCid choiceArg return DsoRules_MergeDevelopmentFundCouponsResult with result From 512ab536aefb242e78230bd8661f310e2fc70a17 Mon Sep 17 00:00:00 2001 From: Jose Velasco - IEU Date: Thu, 13 Nov 2025 09:50:32 +0000 Subject: [PATCH 34/34] cip-82: remove unneeded comment Signed-off-by: Jose Velasco - IEU --- .../Testing/Registries/AmuletRegistry/Parameters.daml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml b/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml index 0da7537b24..6244422ea1 100644 --- a/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml +++ b/token-standard/splice-token-standard-test/daml/Splice/Testing/Registries/AmuletRegistry/Parameters.daml @@ -64,13 +64,6 @@ defaultAmuletDecentralizedSynchronizerConfig = AmuletDecentralizedSynchronizerCo defaultFeaturedAppActivityMarkerAmount : Decimal defaultFeaturedAppActivityMarkerAmount = 1.0 --- Note: In the final PR, `developmentFundConfig` should not be set to None. --- The definition of `defaultAmuletConfig` will change from --- `defaultAmuletConfig : AmuletConfig USD` --- to --- `defaultAmuletConfig : Party -> AmuletConfig USD`, --- where the `Party` parameter represents the Foundation beneficiary. --- -- | Default proposal for issuance curve and tickDuration defaultAmuletConfig : AmuletConfig USD defaultAmuletConfig = AmuletConfig with