Skip to content

Commit 82d47f1

Browse files
committed
Support transfers with 24h validity period
Signed-off-by: Moritz Kiefer <moritz.kiefer@purelyfunctional.org>
1 parent 2896359 commit 82d47f1

File tree

36 files changed

+791
-535
lines changed

36 files changed

+791
-535
lines changed

daml/dars.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,4 @@ splitwell 0.1.6 872da0dd7986fd768930f85d6a7310a94a0ef924e7fbb7bb7a4e149f2b5feb74
146146
splitwell 0.1.7 841d1c9c86b5c8f3a39059459ecd8febedf7703e18f117300bb0ebf4423db096
147147
splitwell 0.1.8 63b8153a08ceb4bf40d807acc5712372c3eac548c266be4d5e92470b4f655515
148148
splitwell 0.1.9 b6267905698d2798b9ef171e27d49fb88e052ec0ec0e0675a3a1b275c7d037d4
149-
splitwell-test 0.1.19 37cafd5988552e48ae5dfd17e2a192eb8d165ae4dcaf1ddb0589c9ac49090aed
149+
splitwell-test 0.1.19 37cafd5988552e48ae5dfd17e2a192eb8d165ae4dcaf1ddb0589c9ac49090aed

daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletBurn.daml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ import Splice.ValidatorLicense
1111

1212
import Splice.Scripts.Util
1313

14+
import Splice.Testing.Registries.AmuletRegistry (advanceToNextRoundChange)
15+
1416
-- | Note to readers: try commenting suffixes of this script out to
1517
-- see the various steps in action.
1618
testAmuletBurn : Script ()
1719
testAmuletBurn = do
1820
-- bootstrap app
1921
app <- setupApp
2022

21-
advanceToNextRoundChange app
23+
advanceToNextRoundChange app.dso
2224

2325
-- -- start issuing
2426
runNextIssuance app

daml/splice-amulet-test/daml/Splice/Scripts/TestAmuletRulesTransfer.daml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import Splice.AmuletRules
1919
import Splice.Expiry
2020
import Splice.Fees
2121
import Splice.Testing.Registries.AmuletRegistry.Parameters
22+
import Splice.Testing.Registries.AmuletRegistry (convertAllFeaturedAppActivityMarkers)
2223
import Splice.Scripts.Util
2324

2425
testUsageFees: Script ()
@@ -467,6 +468,8 @@ testAppRewardBeneficiaries = do
467468
context = context.context
468469
expectedDso = Some app.dso
469470

471+
convertAllFeaturedAppActivityMarkers app.dso
472+
470473
if featured
471474
then do
472475
[(rewardCid, aliceAppRewardCouponUnfeaturedNoBeneficiary)] <- query @AppRewardCoupon alice.primaryParty
@@ -492,6 +495,8 @@ testAppRewardBeneficiaries = do
492495
context = context.context
493496
expectedDso = Some app.dso
494497

498+
convertAllFeaturedAppActivityMarkers app.dso
499+
495500
[(rewardCid1, aliceAppRewardCouponUnfeaturedBeneficiary)] <- queryFilter @AppRewardCoupon alice.primaryParty (\r -> r.beneficiary == Some alice.primaryParty)
496501

497502
[(rewardCid2, bobAppRewardCouponUnfeaturedBeneficiary)] <- queryFilter @AppRewardCoupon bob.primaryParty (\r -> r.beneficiary == Some bob.primaryParty)

daml/splice-amulet-test/daml/Splice/Scripts/TestComputeFees.daml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
module Splice.Scripts.TestComputeFees where
55

66
import DA.Assert
7+
import DA.Time
78
import DA.TextMap as TextMap
89
import Daml.Script
910

@@ -46,10 +47,16 @@ testScaledHoldingFees = do
4647

4748
-- skip 202 rounds so that the amulet expires
4849
skipNRounds app 202
50+
-- advance external party config state contracts to current
51+
-- rounds
52+
updateExternalPartyConfigState app
53+
passTime (hours 24)
54+
updateExternalPartyConfigState app
4955

50-
openRoundCid <- getLatestActiveOpenRound app
51-
result <- submit app.dso $ exerciseCmd curAmuletCid Amulet_Expire with
52-
roundCid = openRoundCid._1
56+
(config0Cid, config1Cid) <- getExternalPartyConfigStates app
57+
result <- submit app.dso $ exerciseCmd curAmuletCid Amulet_ExpireV2 with
58+
externalPartyConfigState0Cid = config0Cid
59+
externalPartyConfigState1Cid = config1Cid
5360

5461
-- Check the expire result. In particular, actual holding fee should be based
5562
-- on the amulet price when the amulet was created, not the current one.
@@ -58,7 +65,7 @@ testScaledHoldingFees = do
5865
round = curAmulet.amount.createdAt
5966
changeToInitialAmountAsOfRoundZero = negate (getValueAsOfRound0 curAmulet.amount)
6067
changeToHoldingFeesRate = negate scaledHoldingFee
61-
expectedMeta = Some $ Metadata $ TextMap.fromList
68+
expectedMeta = Metadata $ TextMap.fromList
6269
[ ("splice.lfdecentralizedtrust.org/burned", show curAmulet.amount.initialAmount)
6370
, ("splice.lfdecentralizedtrust.org/tx-kind","expire-dust")
6471
]

daml/splice-amulet-test/daml/Splice/Scripts/TestDesignExample.daml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Splice.Expiry
1616
import Splice.Types
1717
import Splice.Fees
1818
import Splice.Scripts.Util
19+
import Splice.Testing.Registries.AmuletRegistry (convertAllFeaturedAppActivityMarkers)
1920

2021
arbiterPaymentAmount : Decimal
2122
arbiterPaymentAmount = 1.0
@@ -36,8 +37,8 @@ template Escrow
3637
controller p
3738
do LockedAmulet{lock,amulet=locked} <- fetchButArchiveLater (ForOwner with dso; owner = p) lockedAmulet -- archived as part of unlocking
3839
require "lock holder matches locked amulet " ([arbiter] == lock.holders)
39-
result <- exercise lockedAmulet (LockedAmulet_Unlock transferContext.openMiningRound)
40-
let cid = result.amuletSum.amulet
40+
result <- exercise lockedAmulet LockedAmulet_UnlockV2
41+
let cid = result.amuletCid
4142
let transfer = Transfer with
4243
sender = p
4344
provider = arbiter
@@ -128,7 +129,6 @@ test_designExample = do
128129
holdingFees = 0.0 -- no holding fees charged on transfer
129130
inputAmuletHoldingFees = transferConfig.holdingFee.rate * 2.0 -- there are 2 input amulets
130131
outputAmuletHoldingFees = transferConfig.holdingFee.rate * 3.0 -- there are 2 output amulets + 1 amulet for the change
131-
outputFees = [lockedAmuletFee, transferredAmuletFee]
132132
senderChangeAmount =
133133
refreshAmuletAmount
134134
- arbiterPaymentAmount
@@ -148,7 +148,7 @@ test_designExample = do
148148
inputAmuletAmount = refreshAmuletAmount + lockedAmuletAmount
149149
balanceChanges = Map.empty
150150
holdingFees
151-
outputFees
151+
outputFees = [0.0, 0.0]
152152
senderChangeAmount
153153
senderChangeFee = transferConfig.createFee.fee
154154
amuletPrice = 1.0
@@ -190,6 +190,7 @@ test_designExample = do
190190
in expiringAmount transferConfig.holdingFee amount c2.round
191191

192192
-- check app reward issued by the escrow refresh
193+
convertAllFeaturedAppActivityMarkers app.dso
193194
[(_, appRewardCoupon)] <- queryFilter @AppRewardCoupon app.dso (\reward -> reward.round.number == 50002)
194195
appRewardCoupon === AppRewardCoupon with
195196
dso = app.dso
@@ -198,14 +199,14 @@ test_designExample = do
198199
featured = True
199200
amount =
200201
-- rewards are issued for all output fees, but not the senderChangeFee
201-
sum outputFees +
202202
transferConfig.extraFeaturedAppRewardAmount
203203
round = Round 50002
204204

205205
-- the 'arbiter' also receives featured app rewards when he's the sender and provider of a transfer
206206
runNextIssuance app
207207
let reimbursementAmount = 0.5
208208
pay app arbiter alice.primaryParty reimbursementAmount
209+
convertAllFeaturedAppActivityMarkers app.dso
209210

210211
[(_, appReward)] <- queryFilter @AppRewardCoupon app.dso (\reward -> reward.round.number == 50003)
211212
appReward === AppRewardCoupon with

daml/splice-amulet-test/daml/Splice/Scripts/TestLockAndAmuletExpiry.daml

Lines changed: 46 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@ import Splice.Fees
1616
import Splice.Expiry
1717
import Splice.Round
1818
import Splice.Types
19-
import Splice.RelRound
20-
import Splice.Schedule
2119
import Splice.Testing.Registries.AmuletRegistry.Parameters
20+
import Splice.Testing.Registries.AmuletRegistry (advanceToNextRoundChange)
2221
import Splice.Scripts.Util
2322

2423
import DA.Foldable (forA_)
@@ -34,39 +33,14 @@ scaleAmuletConfig amuletPrice config = AmuletConfig with
3433
transferPreapprovalFee = fmap (/ amuletPrice) config.transferPreapprovalFee
3534
featuredAppActivityMarkerAmount = fmap (/ amuletPrice) config.featuredAppActivityMarkerAmount
3635
optDevelopmentFundManager = config.optDevelopmentFundManager
36+
externalPartyConfigStateTickDuration = config.externalPartyConfigStateTickDuration
3737

3838
test : Script ()
3939
test = script do
4040
DefaultAppWithUsers{..} <- setupDefaultAppWithUsers
41-
advanceToNextRoundChange app -- advance time so we're out of the initial mining round
42-
currentTime <- getTime
41+
advanceToNextRoundChange app.dso -- advance time so we're out of the initial mining round
4342
let amuletPrice = 2.5
4443
config = scaleAmuletConfig amuletPrice defaultAmuletConfig
45-
tickDuration = config.tickDuration
46-
47-
t00_00 = currentTime
48-
t05_00 = addRelRoundN 2 currentTime tickDuration
49-
t07_30 = addRelRoundN 3 currentTime tickDuration
50-
t12_30 = addRelRoundN 5 currentTime tickDuration
51-
t15_00 = addRelRoundN 6 currentTime tickDuration
52-
t17_30 = addRelRoundN 7 currentTime tickDuration
53-
54-
issuingFor0 = days 0
55-
issuingFor3 = issuingFor0 + tickDuration + tickDuration + tickDuration
56-
57-
amuletConfigUsd = defaultAmuletConfig
58-
transferConfigUsd = amuletConfigUsd.transferConfig
59-
issuanceConfig0 = getValueAsOf issuingFor0 amuletConfigUsd.issuanceCurve
60-
issuanceConfig3 = getValueAsOf issuingFor3 amuletConfigUsd.issuanceCurve
61-
openRound0 = OpenMiningRound with dso = app.dso; round = Round 0; amuletPrice; opensAt = t00_00; targetClosesAt = t05_00; issuingFor = issuingFor0; transferConfigUsd; issuanceConfig = issuanceConfig0; tickDuration
62-
openRound3 = openRound0 with round = Round 3; opensAt = t07_30 ; targetClosesAt = t12_30; issuanceConfig = issuanceConfig3
63-
64-
-- Lock expires at T00:12:30
65-
let lockExpiresAt12_30 = TimeLock with holders = [alice.primaryParty]; expiresAt = t12_30; optContext = None
66-
-- Lock expires at T00:15:00
67-
let lockExpiresAt15_00 = TimeLock with holders = [alice.primaryParty]; expiresAt = t15_00; optContext = None
68-
-- Lock expires at T00:17:30
69-
let lockExpiresAt17_30 = TimeLock with holders = [alice.primaryParty]; expiresAt = t17_30; optContext = None
7044

7145
-- This amulet is estimated to expire at round 5.
7246
-- we exploit that there are exactly three open rounds active at any point in time.
@@ -76,105 +50,58 @@ test = script do
7650
let amountExpiresAtRound5 = ExpiringAmount with initialAmount = config.transferConfig.holdingFee.rate * 3.5; createdAt = Round 1; ratePerRound = config.transferConfig.holdingFee
7751
let bounded = amountExpiresAt amountExpiresAtRound5
7852
bounded === Singleton (Round 5)
79-
let (Singleton r) = bounded
80-
estimateOpenRoundCreatedAt config.tickDuration openRound3 (Singleton(addRelRound r (RelRound 2))) === Some (Singleton t15_00)
81-
82-
-- lock expires at T00:12:30 before amulet expires at T00:15:00
83-
let lockExpireBeforeAmulet = doesLockExpireBeforeAmulet openRound3 lockExpiresAt12_30 amountExpiresAtRound5 config.tickDuration
84-
assert lockExpireBeforeAmulet
85-
86-
-- lock expires at T00:15:00 while amulet expires at T00:15:00
87-
let lockExpiresAsAmulet = doesLockExpireBeforeAmulet openRound3 lockExpiresAt15_00 amountExpiresAtRound5 config.tickDuration
88-
assert $ not lockExpiresAsAmulet
89-
90-
-- lock expires at T00:17:30 after amulet expires at T00:15:00
91-
let lockExpiresAfterAmulet = doesLockExpireBeforeAmulet openRound3 lockExpiresAt17_30 amountExpiresAtRound5 config.tickDuration
92-
assert $ not lockExpiresAfterAmulet
93-
94-
let largeAmount = ExpiringAmount with initialAmount = 10000.0; createdAt = Round 1; ratePerRound = config.transferConfig.holdingFee
95-
-- lock expires at T00:12:30 before amulet with large amount, which is regarded as never expires (EstimatedTime.AfterMaxTime)
96-
let lockExpiresBeforeLargeAmulet = doesLockExpireBeforeAmulet openRound3 lockExpiresAt12_30 largeAmount config.tickDuration
97-
assert lockExpiresBeforeLargeAmulet
98-
99-
pure ()
100-
101-
-- From #2336, This test case is to make sure we handle DA.Time overflow
102-
-- in estimateOpenRoundCreatedAt when the amulet amount is too large
103-
testLargeAmuletExpiry : Script ()
104-
testLargeAmuletExpiry = script do
105-
DefaultAppWithUsers{..} <- setupDefaultAppWithUsers
106-
now <- getTime
107-
108-
let amuletPrice = 2.5
109-
configUsd = defaultAmuletConfig
110-
transferConfigAmulet = scaleFees (1.0 / amuletPrice) configUsd.transferConfig
111-
e = ExpiringAmount with initialAmount = 10000.0; createdAt = Round 1; ratePerRound = transferConfigAmulet.holdingFee
112-
Singleton r = amountExpiresAt e
113-
expiringRound = Singleton $ addRelRound r (RelRound 2)
114-
issuingFor = minutes 5
115-
currentMiningRound = OpenMiningRound with
116-
dso = app.dso
117-
round = Round 1
118-
amuletPrice = 1.0
119-
opensAt = now
120-
targetClosesAt = addRelTime now (minutes 5)
121-
issuingFor
122-
transferConfigUsd = configUsd.transferConfig
123-
issuanceConfig = getValueAsOf issuingFor configUsd.issuanceCurve
124-
tickDuration = configUsd.tickDuration
125-
126-
estimateOpenRoundCreatedAt configUsd.tickDuration currentMiningRound expiringRound === Some AfterMaxBound
12753
pure ()
12854

12955
testExpireLockedAmulet : Script ()
13056
testExpireLockedAmulet = do
13157
DefaultAppWithUsers{..} <- setupDefaultAppWithUsers
132-
advanceToNextRoundChange app -- advance time so we're out of the initial mining round
58+
advanceToNextRoundChange app.dso -- advance time so we're out of the initial mining round
13359
passTime (minutes 10) -- pass enough time so that round 2 expires in 10 minutes
13460

135-
-- Locking the amulet with with initialAmount 0.000005 and created at round 2 but with a
136-
-- lock for 10 minutes should fail, as round 2 ends in 10 minutes
137-
getLockedAmuletMustFail app alice bob.primaryParty defaultTransferConfig.holdingFee.rate (minutes 10)
61+
-- We don't enforce that locks expire before the underlying amulet does so locking an amulet with with initialAmount 0.000005 and created at round 2 but with a
62+
-- lock for 10 minutes should succeed even if round 2 ends in 10 minutes.
63+
cid0 <- getLockedAmulet app alice bob.primaryParty defaultTransferConfig.holdingFee.rate (minutes 10)
13864
-- Lock the amulet with initialAmount 0.000005 and created at round 2
13965
-- As the amulet amount is equal to one round's holding fee, it will be expired at round 3
14066
cid <- getLockedAmulet app alice bob.primaryParty defaultTransferConfig.holdingFee.rate (seconds 90 - convertMicrosecondsToRelTime 1)
14167
Some(lockedAmulet) <- queryContractId @LockedAmulet alice.primaryParty cid
14268

143-
roundCid <- advanceRound app (Round 3)
144-
145-
-- Lock is not yet expired
146-
-- DSO party fails to expire the amulet
147-
testChoiceFailed [app.dso] [app.dso] $
148-
exerciseCmd cid (LockedAmulet_ExpireAmulet roundCid)
69+
_ <- advanceRound app (Round 3)
14970

15071
-- Current round is 4
151-
roundCid <- advanceRound app (Round 4)
72+
_ <- advanceRound app (Round 4)
15273

153-
-- Amulet is not yet expired
154-
-- 2 rounds before current round = 4 - 2 = 2 which is before round 3 when the amulet expires
74+
-- Lock is not yet expired
15575
-- DSO party fails to expire the amulet
76+
(externalPartyConfigState0Cid, externalPartyConfigState1Cid) <- getExternalPartyConfigStates app
15677
testChoiceFailed [app.dso] [app.dso] $
157-
exerciseCmd cid (LockedAmulet_ExpireAmulet roundCid)
78+
exerciseCmd cid (LockedAmulet_ExpireAmuletV2 with
79+
externalPartyConfigState0Cid
80+
externalPartyConfigState1Cid)
81+
15882

15983
-- Current round is 5
160-
roundCid <- advanceRound app (Round 5)
84+
_ <- advanceRound app (Round 5)
16185

16286
normalizedBalanceBeforeExpiry <- getNormalizedBalance app.dso
16387

16488
-- Amulet is expired
16589
-- 2 rounds before current round => 5 - 2 = 3 which is equal to round 3 when the amulet expires
16690
-- DSO party expires the amulet
167-
result <- checkTxMetadata app TxKind_ExpireDust alice.primaryParty $
91+
(externalPartyConfigState0Cid, externalPartyConfigState1Cid) <- getExternalPartyConfigStates app
92+
result <- checkTxMetadata' app TxKind_ExpireDust alice.primaryParty $
16893
testChoice [app.dso] [app.dso] $
169-
exerciseCmd cid (LockedAmulet_ExpireAmulet roundCid)
94+
exerciseCmd cid $ LockedAmulet_ExpireAmuletV2 with
95+
externalPartyConfigState0Cid
96+
externalPartyConfigState1Cid
17097
let expireSummary = result.expireSum
17198

17299
normalizedBalanceAfterExpiry <- getNormalizedBalance app.dso
173100
expireSummary.changeToInitialAmountAsOfRoundZero === normalizedBalanceAfterExpiry - normalizedBalanceBeforeExpiry
174101
expireSummary.changeToHoldingFeesRate === - (lockedAmulet.amulet.amount.ratePerRound.rate)
175102

176103
lockedAmulets <- query @LockedAmulet bob.primaryParty
177-
lockedAmulets === []
104+
map fst lockedAmulets === [cid0]
178105

179106
pure ()
180107

@@ -195,31 +122,39 @@ testExpireAmulet = do
195122
amulet.amount.createdAt === Round 1
196123

197124
-- current latest active round is 3
198-
latestActiveRoundCid <- advanceRound app (Round 3)
125+
_ <- advanceRound app (Round 3)
199126

200127
-- 2 rounds before latest active round = 3 - 2 = 1 which is before round 3 when the amulet expires
201128
-- DSO party fails to expire the amulet
129+
(externalPartyConfigState0Cid, externalPartyConfigState1Cid) <- getExternalPartyConfigStates app
202130
testChoiceFailed [app.dso] [app.dso] $
203-
exerciseCmd cid (Amulet_Expire latestActiveRoundCid)
131+
exerciseCmd cid Amulet_ExpireV2 with
132+
externalPartyConfigState0Cid
133+
externalPartyConfigState1Cid
204134

205135
-- current latest active round is 4
206-
latestActiveRoundCid <- advanceRound app (Round 4)
136+
_ <- advanceRound app (Round 4)
207137

208138
-- 2 rounds before current latest active round => 4 - 2 = 2 which is before round 3 when the amulet expires
209139
-- DSO party fails to expire the amulet
140+
(externalPartyConfigState0Cid, externalPartyConfigState1Cid) <- getExternalPartyConfigStates app
210141
testChoiceFailed [app.dso] [app.dso] $
211-
exerciseCmd cid (Amulet_Expire latestActiveRoundCid)
142+
exerciseCmd cid Amulet_ExpireV2 with
143+
externalPartyConfigState0Cid
144+
externalPartyConfigState1Cid
212145

213-
-- current latest active round is 5
214-
latestActiveRoundCid <- advanceRound app (Round 5)
146+
_ <- advanceRound app (Round 5)
215147

216148
normalizedBalanceBeforeExpiry <- getNormalizedBalance app.dso
217149

218150
-- 2 rounds before current latest active round => 5 - 2 = 3 which is equal to round 3 when the amulet expires
219151
-- DSO party expires the amulet
220-
result <- checkTxMetadata app TxKind_ExpireDust alice.primaryParty $
152+
(externalPartyConfigState0Cid, externalPartyConfigState1Cid) <- getExternalPartyConfigStates app
153+
result <- checkTxMetadata' app TxKind_ExpireDust alice.primaryParty $
221154
testChoice [app.dso] [app.dso] $
222-
exerciseCmd cid (Amulet_Expire latestActiveRoundCid)
155+
exerciseCmd cid $ Amulet_ExpireV2 with
156+
externalPartyConfigState0Cid
157+
externalPartyConfigState1Cid
223158
let expireSummary = result.expireSum
224159

225160
normalizedBalanceAfterExpiry <- getNormalizedBalance app.dso
@@ -243,8 +178,16 @@ advanceRound app expectedRound = do
243178
(latestActiveRoundCid, _) <- getLatestActiveOpenRound app
244179
Some(currentRound) <- queryContractId @OpenMiningRound app.dso latestActiveRoundCid
245180
currentRound.round === expectedRound
181+
syncExternalPartyConfigStatesWithRounds app
246182
pure latestActiveRoundCid
247183

184+
syncExternalPartyConfigStatesWithRounds : AmuletApp -> Script ()
185+
syncExternalPartyConfigStatesWithRounds app = do
186+
passTime (hours 24)
187+
updateExternalPartyConfigState app
188+
passTime (hours 24)
189+
updateExternalPartyConfigState app
190+
248191
addRelRoundN : Int -> Time -> RelTime -> Time
249192
addRelRoundN n t relTime
250193
| n <= 0 = t

0 commit comments

Comments
 (0)