Skip to content

Commit 3149865

Browse files
committed
Enable partial assets commit and add documentation
1 parent 8255c21 commit 3149865

File tree

3 files changed

+182
-15
lines changed

3 files changed

+182
-15
lines changed

docs/docs/how-to/incremental-commit.md

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,153 @@ cardano-cli query utxo \
5050
```
5151

5252
:::info
53-
You can also specify `amount` of lovelace you want to commit together with the `UTxO` and hydra-node would
54-
commit only the specified amount and return any leftover to the user address.
53+
You can also specify `amount` of lovelace you want to commit as well as `assets` together with the `UTxO` and hydra-node would
54+
commit only the specified amount of lovelace or any potential non ADA assets and return any leftover to the user address.
5555
:::
5656

57+
:::danger
58+
This functionality should be treated as **experimental** for the time being until we see some user reports that this API is
59+
working well and is easy to use.
60+
:::
61+
62+
<details>
63+
<summary>Partial commit example: </summary>
64+
65+
If there exists a user UTxO that looks like this
66+
67+
```json
68+
{"0bdf069df8fa1084989a7bda419c900810fd5d5a72a95f7ab487f96df9052fb8#0":
69+
{ "address":"addr_test1vq7j5vf74jw779y6ssxk2rwart5mltr2r7ju4gtfc3kcawcy0v2v8",
70+
"datum":null,
71+
"datumhash":null,
72+
"inlineDatum":null,
73+
"inlineDatumRaw":null,
74+
"referenceScript":null,
75+
"value":
76+
{ "dcf5fdd1d01c04b0e6262bba173a89c4b81b6570211f08bc059c8a75":
77+
{ "34":2007797983096461947,
78+
"7041ef64f4476c6f6d8a3c74b7":4866145859671050979,
79+
"b7a7b64d585f973e82be6ca36ae948":2,
80+
"beb7e61cb67dd301":2
81+
},
82+
"lovelace":5000000
83+
}
84+
}
85+
}
86+
87+
```
88+
Then the user can decide to commit some amount of lovelace and assets while the _change_ would be given back to the origin address.
89+
In order to do that they can send a http POST request to the `/commit` endpoint
90+
specifying the _amount_ of ADA and map of _tokens_ with quantities to commit:
91+
92+
```json
93+
94+
{
95+
"amount":3000000,
96+
"tokens":
97+
[
98+
[ "dcf5fdd1d01c04b0e6262bba173a89c4b81b6570211f08bc059c8a75",
99+
{ "34":2007797983096461947,
100+
"7041ef64f4476c6f6d8a3c74b7":4866145859671050979
101+
}
102+
]
103+
],
104+
"utxoToCommit":
105+
{ "0bdf069df8fa1084989a7bda419c900810fd5d5a72a95f7ab487f96df9052fb8#0":
106+
{ "address":"addr_test1vq7j5vf74jw779y6ssxk2rwart5mltr2r7ju4gtfc3kcawcy0v2v8",
107+
"datum":null,
108+
"datumhash":null,
109+
"inlineDatum":null,
110+
"inlineDatumRaw":null,
111+
"referenceScript":null,
112+
"value":
113+
{ "dcf5fdd1d01c04b0e6262bba173a89c4b81b6570211f08bc059c8a75":
114+
{ "34":2007797983096461947,
115+
"7041ef64f4476c6f6d8a3c74b7":4866145859671050979,
116+
"b7a7b64d585f973e82be6ca36ae948":2,
117+
"beb7e61cb67dd301":2
118+
},
119+
"lovelace":5000000
120+
}
121+
}
122+
}
123+
}
124+
125+
```
126+
Hydra node returns a deposit transaction which then needs to be signed and submitted to the network:
127+
128+
```
129+
130+
"d22e92621c1b66d947eff97887feb19c1fb5751ecb7440cd6749ac8d734af04f"
131+
132+
== INPUTS (2)
133+
- 0bdf069df8fa1084989a7bda419c900810fd5d5a72a95f7ab487f96df9052fb8#0
134+
ShelleyAddress Testnet (KeyHashObj (KeyHash {unKeyHash = "3d2a313eac9def149a840d650ddd1ae9bfac6a1fa5caa169c46d8ebb"})) StakeRefNull
135+
5000000 lovelace
136+
2007797983096461947 dcf5fdd1d01c04b0e6262bba173a89c4b81b6570211f08bc059c8a75.34
137+
4866145859671050979 dcf5fdd1d01c04b0e6262bba173a89c4b81b6570211f08bc059c8a75.7041ef64f4476c6f6d8a3c74b7
138+
2 dcf5fdd1d01c04b0e6262bba173a89c4b81b6570211f08bc059c8a75.b7a7b64d585f973e82be6ca36ae948
139+
2 dcf5fdd1d01c04b0e6262bba173a89c4b81b6570211f08bc059c8a75.beb7e61cb67dd301
140+
TxOutDatumNone
141+
ReferenceScriptNone
142+
- 799028d0132ec355d6870704e040b73dbab67dba40313cb4f5dbc42d0e66bd40#1
143+
144+
== COLLATERAL INPUTS (1)
145+
- 799028d0132ec355d6870704e040b73dbab67dba40313cb4f5dbc42d0e66bd40#1
146+
147+
== REFERENCE INPUTS (0)
148+
149+
== OUTPUTS (3)
150+
Total number of assets: 5
151+
- ShelleyAddress Testnet (ScriptHashObj (ScriptHash "ae01dade3a9c346d5c93ae3ce339412b90a0b8f83f94ec6baa24e30c")) StakeRefNull
152+
3000000 lovelace
153+
2007797983096461947 dcf5fdd1d01c04b0e6262bba173a89c4b81b6570211f08bc059c8a75.34
154+
4866145859671050979 dcf5fdd1d01c04b0e6262bba173a89c4b81b6570211f08bc059c8a75.7041ef64f4476c6f6d8a3c74b7
155+
TxOutDatumInline [0,["0x3fbb97e02ed6f34def7b87d9aaff67b4fe707f338d72bc2a7f93a409",1757516160812,[[0,[[0,["0x0bdf069df8fa1084989a7bda419c900810fd5d5a72a95f7ab487f96df9052fb8",0]],"0xd8799fd8799fd8799f581c3d2a313eac9def149a840d650ddd1ae9bfac6a1fa5caa169c46d8ebbffd87a80ffa240a1401a002dc6c0581cdcf5fdd1d01c04b0e6262bba173a89c4b81b6570211f08bc059c8a75a241341b1bdd21a0bb35da7b4d7041ef64f4476c6f6d8a3c74b71b438805e09eee4ae3d87980d87a80ff"]]]]]
156+
- ShelleyAddress Testnet (KeyHashObj (KeyHash {unKeyHash = "3d2a313eac9def149a840d650ddd1ae9bfac6a1fa5caa169c46d8ebb"})) StakeRefNull
157+
2000000 lovelace
158+
2 dcf5fdd1d01c04b0e6262bba173a89c4b81b6570211f08bc059c8a75.b7a7b64d585f973e82be6ca36ae948
159+
2 dcf5fdd1d01c04b0e6262bba173a89c4b81b6570211f08bc059c8a75.beb7e61cb67dd301
160+
TxOutDatumNone
161+
- ShelleyAddress Testnet (KeyHashObj (KeyHash {unKeyHash = "f8a68cd18e59a6ace848155a0e967af64f4d00cf8acee8adc95a6b0d"})) StakeRefNull
162+
19625359 lovelace
163+
TxOutDatumNone
164+
165+
== TOTAL COLLATERAL
166+
TxTotalCollateralNone
167+
168+
== RETURN COLLATERAL
169+
TxReturnCollateralNone
170+
171+
== FEE
172+
TxFeeExplicit ShelleyBasedEraConway (Coin 197973)
173+
174+
== VALIDITY
175+
TxValidityNoLowerBound
176+
TxValidityUpperBound ShelleyBasedEraConway (Just (SlotNo 38))
177+
178+
== MINT/BURN
179+
0 lovelace
180+
181+
== SCRIPTS (0)
182+
Total size (bytes): 0
183+
184+
== DATUMS (0)
185+
186+
== REDEEMERS (0)
187+
188+
== REQUIRED SIGNERS
189+
[]
190+
191+
== METADATA
192+
TxMetadataInEra ShelleyBasedEraConway (TxMetadata {unTxMetadata = fromList [(55555,TxMetaText "HydraV1/DepositTx")]})
193+
```
194+
195+
If you take a look at the outputs you will see that we only locked specified ADA amount + tokens at the deposit address and gave back any leftover to the
196+
user address.
197+
198+
</details>
199+
57200
Then a request to the `/commit` endpoint provides us with a transaction:
58201

59202
```shell

hydra-cluster/src/Hydra/Cluster/Scenarios.hs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,24 +1342,28 @@ canDepositPartially tracer workDir blockTime backend hydraScriptsTxId =
13421342
(walletVk, walletSk) <- generate genKeyPair
13431343

13441344
tokensUTxO <- generate (genUTxOWithAssetsSized 2 (Just $ PolicyId $ CAPI.hashScript $ CAPI.PlutusScript dummyMintingScript))
1345+
let assetsToValue = foldMap ((mempty <>) . uncurry policyAssetsToValue) . Map.toList
13451346
let totalTokenValue = UTxO.totalValue tokensUTxO
13461347
let tokenAssets = valueToPolicyAssets totalTokenValue
1347-
let tokenAssetValue = foldMap ((mempty <>) . uncurry policyAssetsToValue) (Map.toList tokenAssets)
1348+
let tokenAssetValue = assetsToValue tokenAssets
1349+
let partialTokenAssets = Map.map (\(CAPI.PolicyAssets policyAssetMap) -> CAPI.PolicyAssets $ Map.filter (> 20) policyAssetMap) tokenAssets
1350+
let partialTokenAssetValue = assetsToValue partialTokenAssets
1351+
let tokenAssetValueWithoutAda = assetsToValue $ valueToPolicyAssets partialTokenAssetValue
13481352
let seedAmount = 5_000_000
13491353
-- NOTE: We (and also the users) need to make sure we give enough ADA when committing. If deposit tx ADA amount is too low
13501354
-- and some ADA is added to it after balancing in the wallet, then we have problems matching on the 'CommitApproved' etc.
13511355
let commitAmount = 3_000_000
13521356
commitUTxOWithoutTokens <- seedFromFaucet backend walletVk (lovelaceToValue seedAmount) (contramap FromFaucet tracer)
1353-
commitUTxOWithTokens <- seedFromFaucet backend walletVk (lovelaceToValue seedAmount <> totalTokenValue) (contramap FromFaucet tracer)
1357+
commitUTxOWithTokens <- seedFromFaucet backend walletVk (lovelaceToValue seedAmount <> tokenAssetValue) (contramap FromFaucet tracer)
13541358
-- This one is expected to fail since there is 5 ADA at the wallet address but we specified 6 ADA to commit
13551359
(requestCommitTx' n1 commitUTxOWithoutTokens (Just 8_000_000) Nothing <&> toJSON)
13561360
`shouldThrow` expectErrorStatus 400 (Just "AmountTooLow")
13571361

13581362
-- This one is expected to fail since there are no extra assets but we specified some to commit
1359-
(requestCommitTx' n1 commitUTxOWithoutTokens (Just 5_000_000) (Just tokenAssets) <&> toJSON)
1363+
(requestCommitTx' n1 commitUTxOWithoutTokens (Just 5_000_000) (Just partialTokenAssets) <&> toJSON)
13601364
`shouldThrow` expectErrorStatus 400 (Just "InvalidTokenRequest")
13611365

1362-
depositTransaction <- requestCommitTx' n1 commitUTxOWithTokens (Just commitAmount) (Just tokenAssets)
1366+
depositTransaction <- requestCommitTx' n1 commitUTxOWithTokens (Just commitAmount) (Just partialTokenAssets)
13631367
let tx = signTx walletSk depositTransaction
13641368

13651369
Backend.submitTransaction backend tx
@@ -1369,9 +1373,8 @@ canDepositPartially tracer workDir blockTime backend hydraScriptsTxId =
13691373
UTxO.fromList $
13701374
second
13711375
( modifyTxOutValue
1372-
( \v ->
1373-
let assets = Map.toList $ valueToPolicyAssets v
1374-
in lovelaceToValue commitAmount <> foldMap ((mempty <>) . uncurry policyAssetsToValue) assets
1376+
( \_ ->
1377+
lovelaceToValue commitAmount <> tokenAssetValueWithoutAda
13751378
)
13761379
)
13771380
<$> UTxO.toList commitUTxOWithTokens
@@ -1382,9 +1385,6 @@ canDepositPartially tracer workDir blockTime backend hydraScriptsTxId =
13821385
output "CommitFinalized" ["headId" .= headId, "depositTxId" .= getTxId (getTxBody tx)]
13831386

13841387
getSnapshotUTxO n1 `shouldReturn` expectedDeposit
1385-
-- check that user balance contains the change from the commit tx + commitAmount in the UTxO we didn't commit
1386-
(balance <$> Backend.queryUTxOFor backend QueryTip walletVk)
1387-
`shouldReturn` lovelaceToValue seedAmount
13881388

13891389
send n2 $ input "Close" []
13901390

@@ -1402,7 +1402,8 @@ canDepositPartially tracer workDir blockTime backend hydraScriptsTxId =
14021402

14031403
-- Assert final wallet balance
14041404
(balance <$> Backend.queryUTxOFor backend QueryTip walletVk)
1405-
`shouldReturn` lovelaceToValue (seedAmount + commitAmount)
1405+
-- NOTE: in the end we expect seedAmount * 2 since we seeded from faucet twice + assets we minted
1406+
`shouldReturn` lovelaceToValue (seedAmount * 2)
14061407
<> tokenAssetValue
14071408
where
14081409
hydraTracer = contramap FromHydraNode tracer

hydra-tx/src/Hydra/Tx/Deposit.hs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Hydra.Contract.Commit qualified as Commit
1616
import Hydra.Contract.Deposit qualified as Deposit
1717
import Hydra.Plutus (depositValidatorScript)
1818
import Hydra.Plutus.Extras.Time (posixFromUTCTime, posixToUTCTime)
19-
import Hydra.Tx (CommitBlueprintTx (..), HeadId, currencySymbolToHeadId, headIdToCurrencySymbol, txId, withoutUTxO)
19+
import Hydra.Tx (CommitBlueprintTx (..), HeadId, currencySymbolToHeadId, headIdToCurrencySymbol, txId)
2020
import Hydra.Tx.Utils (addMetadata, mkHydraHeadV1TxName)
2121
import PlutusLedgerApi.V3 (POSIXTime)
2222

@@ -59,7 +59,7 @@ depositTx networkId headId commitBlueprintTx upperSlot deadline amount tokens =
5959
utxoToDeposit = mergeUTxO utxoToDeposit' tokensToDepositUTxO
6060

6161
returnToUser =
62-
let returnToUserUTxO = leftoverUTxO `withoutUTxO` tokensToDepositUTxO
62+
let returnToUserUTxO = leftoverUTxO `filterExistingAssets` tokensToDepositUTxO
6363
in if UTxO.null returnToUserUTxO
6464
then StrictSeq.empty
6565
else
@@ -72,6 +72,29 @@ depositTx networkId headId commitBlueprintTx upperSlot deadline amount tokens =
7272

7373
depositInputs = (,BuildTxWith $ KeyWitness KeyWitnessForSpending) <$> depositInputsList
7474

75+
-- | Filter the first argument UTxO's non ADA assets in case any asset exists in the second UTxO argument.
76+
-- Asset quantities will be subtracted if they are found.
77+
filterExistingAssets :: UTxO -> UTxO -> UTxO
78+
filterExistingAssets utxoToFilter utxoToLookup =
79+
UTxO.fromList $ findAssets <$> UTxO.toList utxoToFilter
80+
where
81+
findAssets (i, TxOut a val d r) =
82+
let assets = valueToPolicyAssets val
83+
originalLovelace = selectLovelace val
84+
filteredAssets =
85+
foldMap (uncurry policyAssetsToValue) $
86+
Map.assocs $
87+
Map.mapWithKey
88+
( \pid (PolicyAssets x) ->
89+
let samePid = List.filter (\(pid', _) -> pid' == pid) forLookup
90+
in case List.lookup pid samePid of
91+
Nothing -> PolicyAssets x
92+
Just (PolicyAssets foundAssets) -> PolicyAssets $ x `Map.difference` foundAssets
93+
)
94+
assets
95+
in (i, TxOut a (lovelaceToValue originalLovelace <> filteredAssets) d r)
96+
forLookup = concatMap (Map.toList . valueToPolicyAssets . txOutValue . snd) $ UTxO.toList utxoToLookup
97+
7598
-- | Merges the two 'UTxO' favoring data coming from the first argument 'UTxO'.
7699
-- In case the same 'TxIn' was found in the first 'UTxO' - second 'UTxO' value
77100
-- will be appended to the input.

0 commit comments

Comments
 (0)