Skip to content

Commit 73b6df9

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

File tree

36 files changed

+791
-543
lines changed

36 files changed

+791
-543
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: 53 additions & 111 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,63 @@ 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)
69+
_ <- advanceRound app (Round 3)
70+
updateExternalPartyConfigState app
71+
passTime (hours 24)
72+
updateExternalPartyConfigState app
14473

14574
-- Lock is not yet expired
14675
-- DSO party fails to expire the amulet
76+
(externalPartyConfigState0Cid, externalPartyConfigState1Cid) <- getExternalPartyConfigStates app
14777
testChoiceFailed [app.dso] [app.dso] $
148-
exerciseCmd cid (LockedAmulet_ExpireAmulet roundCid)
78+
exerciseCmd cid (LockedAmulet_ExpireAmuletV2 with
79+
externalPartyConfigState0Cid
80+
externalPartyConfigState1Cid)
14981

150-
-- Current round is 4
151-
roundCid <- advanceRound app (Round 4)
82+
debug "past first"
15283

153-
-- Amulet is not yet expired
154-
-- 2 rounds before current round = 4 - 2 = 2 which is before round 3 when the amulet expires
155-
-- DSO party fails to expire the amulet
156-
testChoiceFailed [app.dso] [app.dso] $
157-
exerciseCmd cid (LockedAmulet_ExpireAmulet roundCid)
158-
159-
-- Current round is 5
160-
roundCid <- advanceRound app (Round 5)
84+
-- Current round is 4
85+
_ <- advanceRound app (Round 4)
86+
passTime (hours 24)
87+
updateExternalPartyConfigState app
88+
passTime (hours 24)
89+
updateExternalPartyConfigState app
16190

16291
normalizedBalanceBeforeExpiry <- getNormalizedBalance app.dso
16392

16493
-- Amulet is expired
16594
-- 2 rounds before current round => 5 - 2 = 3 which is equal to round 3 when the amulet expires
16695
-- DSO party expires the amulet
167-
result <- checkTxMetadata app TxKind_ExpireDust alice.primaryParty $
96+
(externalPartyConfigState0Cid, externalPartyConfigState1Cid) <- getExternalPartyConfigStates app
97+
result <- checkTxMetadata' app TxKind_ExpireDust alice.primaryParty $
16898
testChoice [app.dso] [app.dso] $
169-
exerciseCmd cid (LockedAmulet_ExpireAmulet roundCid)
99+
exerciseCmd cid $ LockedAmulet_ExpireAmuletV2 with
100+
externalPartyConfigState0Cid
101+
externalPartyConfigState1Cid
170102
let expireSummary = result.expireSum
171103

172104
normalizedBalanceAfterExpiry <- getNormalizedBalance app.dso
173105
expireSummary.changeToInitialAmountAsOfRoundZero === normalizedBalanceAfterExpiry - normalizedBalanceBeforeExpiry
174106
expireSummary.changeToHoldingFeesRate === - (lockedAmulet.amulet.amount.ratePerRound.rate)
175107

176108
lockedAmulets <- query @LockedAmulet bob.primaryParty
177-
lockedAmulets === []
109+
map fst lockedAmulets === [cid0]
178110

179111
pure ()
180112

@@ -195,41 +127,51 @@ testExpireAmulet = do
195127
amulet.amount.createdAt === Round 1
196128

197129
-- current latest active round is 3
198-
latestActiveRoundCid <- advanceRound app (Round 3)
130+
_ <- advanceRound app (Round 3)
131+
updateExternalPartyConfigState app
132+
passTime (hours 24)
133+
updateExternalPartyConfigState app
199134

200135
-- 2 rounds before latest active round = 3 - 2 = 1 which is before round 3 when the amulet expires
201136
-- DSO party fails to expire the amulet
137+
(externalPartyConfigState0Cid, externalPartyConfigState1Cid) <- getExternalPartyConfigStates app
202138
testChoiceFailed [app.dso] [app.dso] $
203-
exerciseCmd cid (Amulet_Expire latestActiveRoundCid)
139+
exerciseCmd cid Amulet_ExpireV2 with
140+
externalPartyConfigState0Cid
141+
externalPartyConfigState1Cid
204142

205143
-- current latest active round is 4
206-
latestActiveRoundCid <- advanceRound app (Round 4)
207-
208-
-- 2 rounds before current latest active round => 4 - 2 = 2 which is before round 3 when the amulet expires
209-
-- DSO party fails to expire the amulet
210-
testChoiceFailed [app.dso] [app.dso] $
211-
exerciseCmd cid (Amulet_Expire latestActiveRoundCid)
212-
213-
-- current latest active round is 5
214-
latestActiveRoundCid <- advanceRound app (Round 5)
144+
_ <- advanceRound app (Round 4)
145+
passTime (hours 24)
146+
updateExternalPartyConfigState app
147+
passTime (hours 24)
148+
updateExternalPartyConfigState app
215149

216150
normalizedBalanceBeforeExpiry <- getNormalizedBalance app.dso
217151

152+
passTime (hours 24)
153+
updateExternalPartyConfigState app
154+
passTime (hours 24)
155+
updateExternalPartyConfigState app
156+
218157
-- 2 rounds before current latest active round => 5 - 2 = 3 which is equal to round 3 when the amulet expires
219158
-- DSO party expires the amulet
220-
result <- checkTxMetadata app TxKind_ExpireDust alice.primaryParty $
159+
(externalPartyConfigState0Cid, externalPartyConfigState1Cid) <- getExternalPartyConfigStates app
160+
result <- checkTxMetadata' app TxKind_ExpireDust alice.primaryParty $
221161
testChoice [app.dso] [app.dso] $
222-
exerciseCmd cid (Amulet_Expire latestActiveRoundCid)
162+
exerciseCmd cid $ Amulet_ExpireV2 with
163+
externalPartyConfigState0Cid
164+
externalPartyConfigState1Cid
223165
let expireSummary = result.expireSum
224166

225167
normalizedBalanceAfterExpiry <- getNormalizedBalance app.dso
226168
expireSummary.changeToInitialAmountAsOfRoundZero === normalizedBalanceAfterExpiry - normalizedBalanceBeforeExpiry
227169
expireSummary.changeToHoldingFeesRate === - (amulet.amount.ratePerRound.rate)
228170

229-
-- current active current mining round is 3, 4 and 5
171+
-- current active current mining round is 2, 3 and 4
230172
allActiveOpenMiningRoundCids <- query @OpenMiningRound app.dso
231173
forA_ allActiveOpenMiningRoundCids $ \(_, openRound) -> do
232-
assert $ openRound.round.number >= 3 && openRound.round.number <= 5
174+
assert $ openRound.round.number >= 2 && openRound.round.number <= 4
233175

234176
-- alice now only owns a amulet with initial amount of 1.0
235177
[(_, onlyAmulet)] <- query @Amulet alice.primaryParty

0 commit comments

Comments
 (0)