Skip to content

Commit a19757b

Browse files
committed
refactor: test files, and modules
1 parent a93682b commit a19757b

17 files changed

+1097
-921
lines changed

packages/evolution/src/core/Assets/index.ts

Lines changed: 93 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,23 @@ import * as CBOR from "../CBOR.js"
55
import * as Coin from "../Coin.js"
66
import * as MultiAsset from "../MultiAsset.js"
77
import * as PolicyId from "../PolicyId.js"
8-
import * as PositiveCoin from "../PositiveCoin.js"
8+
99

1010
/**
1111
* Assets representing both ADA and native tokens.
1212
*
13-
* This is a simplified, unified structure where:
14-
* - `coin` always represents the ADA/Lovelace amount
15-
* - `multiAsset` optionally contains native tokens
13+
* This is a **base type** with no constraints on values.
14+
* Lovelace and token quantities can be positive, negative, or zero
15+
* to support arithmetic operations (merge, subtract, negate).
1616
*
17-
* CDDL spec: `value = coin / [coin, multiasset<positive_coin>]`
17+
* Constraints (positive values) are applied at boundaries like
18+
* `TransactionOutput` where CDDL requires `value = coin / [coin, multiasset<positive_coin>]`.
1819
*
1920
* @since 2.0.0
2021
* @category model
2122
*/
2223
export class Assets extends Schema.Class<Assets>("Assets")({
23-
lovelace: Coin.Coin,
24+
lovelace: Schema.BigInt,
2425
multiAsset: Schema.optional(MultiAsset.MultiAsset)
2526
}) {
2627
toJSON() {
@@ -60,15 +61,15 @@ export class Assets extends Schema.Class<Assets>("Assets")({
6061
* @since 2.0.0
6162
* @category constructors
6263
*/
63-
export const fromLovelace = (lovelace: Coin.Coin): Assets => new Assets({ lovelace })
64+
export const fromLovelace = (lovelace: bigint): Assets => new Assets({ lovelace })
6465

6566
/**
6667
* Create Assets containing ADA and native tokens.
6768
*
6869
* @since 2.0.0
6970
* @category constructors
7071
*/
71-
export const withMultiAsset = (lovelace: Coin.Coin, multiAsset: MultiAsset.MultiAsset): Assets =>
72+
export const withMultiAsset = (lovelace: bigint, multiAsset: MultiAsset.MultiAsset): Assets =>
7273
new Assets({ lovelace, multiAsset })
7374

7475
/**
@@ -80,8 +81,8 @@ export const withMultiAsset = (lovelace: Coin.Coin, multiAsset: MultiAsset.Multi
8081
export const fromAsset = (
8182
policyId: PolicyId.PolicyId,
8283
assetName: AssetName.AssetName,
83-
quantity: PositiveCoin.PositiveCoin,
84-
lovelace: Coin.Coin = 0n
84+
quantity: bigint,
85+
lovelace: bigint = 0n
8586
): Assets => {
8687
const assetMap = new Map([[assetName, quantity]])
8788
const multiAsset = new MultiAsset.MultiAsset({ map: new Map([[policyId, assetMap]]) })
@@ -97,7 +98,7 @@ export const fromAsset = (
9798
export const fromUnit = (
9899
unit: string,
99100
quantity: bigint,
100-
lovelace: Coin.Coin = 0n
101+
lovelace: bigint = 0n
101102
): Eff.Effect<Assets, Error> =>
102103
Eff.gen(function* () {
103104
// Parse "policyId.assetName" or "policyId" (empty asset name)
@@ -115,7 +116,7 @@ export const fromUnit = (
115116
: new Uint8Array(0)
116117
const assetName = new AssetName.AssetName({ bytes: assetNameBytes })
117118

118-
return fromAsset(policyId, assetName, quantity as PositiveCoin.PositiveCoin, lovelace)
119+
return fromAsset(policyId, assetName, quantity, lovelace)
119120
})
120121

121122
/**
@@ -129,19 +130,48 @@ export const fromHexStrings = (
129130
policyIdHex: string,
130131
assetNameHex: string,
131132
quantity: bigint,
132-
lovelace: Coin.Coin = 0n
133+
lovelace: bigint = 0n
133134
): Assets => {
134-
// Decode policy ID from hex (28 bytes = 56 hex chars)
135-
const policyIdBytes = new Uint8Array(Buffer.from(policyIdHex, "hex"))
136-
const policyId = new PolicyId.PolicyId({ hash: policyIdBytes })
135+
// Use schema-validated parsers (will throw on invalid hex/length)
136+
const policyId = PolicyId.fromHex(policyIdHex)
137+
const assetName = AssetName.fromHex(assetNameHex)
138+
139+
return fromAsset(policyId, assetName, quantity, lovelace)
140+
}
141+
142+
/**
143+
* Create Assets from a record format (for convenience/testing).
144+
*
145+
* Record format:
146+
* - `lovelace`: bigint for ADA amount
147+
* - `"<policyIdHex><assetNameHex>"`: bigint for native asset quantity
148+
* where policyId is exactly 56 hex chars and assetName is remaining hex chars
149+
*
150+
* @example
151+
* ```ts
152+
* const assets = fromRecord({
153+
* lovelace: 5_000_000n,
154+
* "aabbcc...def456aabbccdd": 100n // 56 char policyId hex + assetName hex
155+
* })
156+
* ```
157+
*
158+
* @since 2.0.0
159+
* @category constructors
160+
*/
161+
export const fromRecord = (record: Record<string, bigint>): Assets => {
162+
let result = fromLovelace(record.lovelace ?? 0n)
137163

138-
// Decode asset name from hex (empty string yields empty bytes)
139-
const assetNameBytes = assetNameHex
140-
? new Uint8Array(Buffer.from(assetNameHex, "hex"))
141-
: new Uint8Array(0)
142-
const assetName = new AssetName.AssetName({ bytes: assetNameBytes })
164+
for (const [key, value] of Object.entries(record)) {
165+
if (key === "lovelace") continue
166+
167+
// First 56 chars are policyId, rest is assetName
168+
// Schema validation in addByHex will handle invalid inputs
169+
const policyIdHex = key.slice(0, 56)
170+
const assetNameHex = key.slice(56)
171+
result = addByHex(result, policyIdHex, assetNameHex, value)
172+
}
143173

144-
return fromAsset(policyId, assetName, quantity as PositiveCoin.PositiveCoin, lovelace)
174+
return result
145175
}
146176

147177
/**
@@ -162,7 +192,7 @@ export const zero: Assets = new Assets({ lovelace: 0n })
162192
* @since 2.0.0
163193
* @category inspection
164194
*/
165-
export const lovelaceOf = (assets: Assets): Coin.Coin => assets.lovelace
195+
export const lovelaceOf = (assets: Assets): bigint => assets.lovelace
166196

167197
/**
168198
* Check if Assets contains native tokens.
@@ -188,6 +218,30 @@ export const getMultiAsset = (assets: Assets): MultiAsset.MultiAsset | undefined
188218
*/
189219
export const isZero = (assets: Assets): boolean => assets.lovelace === 0n && !hasMultiAsset(assets)
190220

221+
/**
222+
* Check if all quantities are positive (lovelace >= 0, tokens > 0).
223+
* Used for validation at transaction output boundaries per CDDL:
224+
* `value = coin / [coin, multiasset<positive_coin>]`
225+
*
226+
* @since 2.0.0
227+
* @category inspection
228+
*/
229+
export const allPositive = (assets: Assets): boolean => {
230+
// Lovelace must be non-negative
231+
if (assets.lovelace < 0n) return false
232+
233+
// All token quantities must be positive
234+
if (assets.multiAsset) {
235+
for (const [, assetMap] of assets.multiAsset.map.entries()) {
236+
for (const [, quantity] of assetMap.entries()) {
237+
if (quantity <= 0n) return false
238+
}
239+
}
240+
}
241+
242+
return true
243+
}
244+
191245
/**
192246
* Get quantity of a specific asset.
193247
*
@@ -244,7 +298,7 @@ export const tokens = (assets: Assets, policyId: PolicyId.PolicyId): Map<AssetNa
244298
* @category combining
245299
*/
246300
export const merge = (a: Assets, b: Assets): Assets => {
247-
const totalLovelace = Coin.add(a.lovelace, b.lovelace)
301+
const totalLovelace = a.lovelace + b.lovelace
248302

249303
// Both have no multiAsset
250304
if (!a.multiAsset && !b.multiAsset) {
@@ -282,7 +336,7 @@ export const add = (
282336
assetName: AssetName.AssetName,
283337
quantity: bigint
284338
): Assets => {
285-
const toAdd = fromAsset(policyId, assetName, quantity as PositiveCoin.PositiveCoin, 0n)
339+
const toAdd = fromAsset(policyId, assetName, quantity, 0n)
286340
return merge(assets, toAdd)
287341
}
288342

@@ -317,9 +371,9 @@ export const negate = (assets: Assets): Assets => {
317371

318372
const negatedMap = new Map<PolicyId.PolicyId, MultiAsset.AssetMap>()
319373
for (const [policyId, assetMap] of assets.multiAsset.map.entries()) {
320-
const negatedAssets = new Map<AssetName.AssetName, PositiveCoin.PositiveCoin>()
374+
const negatedAssets = new Map<AssetName.AssetName, bigint>()
321375
for (const [assetName, quantity] of assetMap.entries()) {
322-
negatedAssets.set(assetName, -quantity as PositiveCoin.PositiveCoin)
376+
negatedAssets.set(assetName, -quantity)
323377
}
324378
negatedMap.set(policyId, negatedAssets)
325379
}
@@ -349,7 +403,7 @@ export const withoutLovelace = (assets: Assets): Assets => {
349403
* @since 2.0.0
350404
* @category combining
351405
*/
352-
export const withLovelace = (assets: Assets, lovelace: Coin.Coin): Assets => {
406+
export const withLovelace = (assets: Assets, lovelace: bigint): Assets => {
353407
return new Assets({ lovelace, multiAsset: assets.multiAsset })
354408
}
355409

@@ -359,7 +413,7 @@ export const withLovelace = (assets: Assets, lovelace: Coin.Coin): Assets => {
359413
* @since 2.0.0
360414
* @category combining
361415
*/
362-
export const addLovelace = (assets: Assets, additionalLovelace: Coin.Coin): Assets => {
416+
export const addLovelace = (assets: Assets, additionalLovelace: bigint): Assets => {
363417
return new Assets({ lovelace: assets.lovelace + additionalLovelace, multiAsset: assets.multiAsset })
364418
}
365419

@@ -369,7 +423,7 @@ export const addLovelace = (assets: Assets, additionalLovelace: Coin.Coin): Asse
369423
* @since 2.0.0
370424
* @category combining
371425
*/
372-
export const subtractLovelace = (assets: Assets, lovelaceToSubtract: Coin.Coin): Assets => {
426+
export const subtractLovelace = (assets: Assets, lovelaceToSubtract: bigint): Assets => {
373427
return new Assets({ lovelace: assets.lovelace - lovelaceToSubtract, multiAsset: assets.multiAsset })
374428
}
375429

@@ -402,7 +456,7 @@ export const filter = (assets: Assets, predicate: (unit: string, amount: bigint)
402456
const filteredMap = new Map<PolicyId.PolicyId, MultiAsset.AssetMap>()
403457
for (const [policyId, assetMap] of assets.multiAsset.map.entries()) {
404458
const policyIdHex = PolicyId.toHex(policyId)
405-
const filteredAssets = new Map<AssetName.AssetName, PositiveCoin.PositiveCoin>()
459+
const filteredAssets = new Map<AssetName.AssetName, bigint>()
406460
for (const [assetName, quantity] of assetMap.entries()) {
407461
const assetNameHex = AssetName.toHex(assetName)
408462
const unit = assetNameHex ? `${policyIdHex}.${assetNameHex}` : policyIdHex
@@ -507,9 +561,12 @@ export const getUnits = (assets: Assets): Array<string> => {
507561
if (assets.multiAsset) {
508562
for (const [policyId, assetMap] of assets.multiAsset.map.entries()) {
509563
const policyIdHex = PolicyId.toHex(policyId)
510-
for (const [assetName] of assetMap.entries()) {
511-
const assetNameHex = AssetName.toHex(assetName)
512-
units.push(`${policyIdHex}${assetNameHex}`)
564+
for (const [assetName, amount] of assetMap.entries()) {
565+
// Only include units with non-zero amounts
566+
if (amount !== 0n) {
567+
const assetNameHex = AssetName.toHex(assetName)
568+
units.push(`${policyIdHex}${assetNameHex}`)
569+
}
513570
}
514571
}
515572
}
@@ -644,11 +701,10 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Ass
644701
for (const [policyIdBytes, assetMapCddl] of multiAssetCddl.entries()) {
645702
const policyId = yield* ParseResult.decode(PolicyId.FromBytes)(policyIdBytes)
646703

647-
const assetMap = new Map<AssetName.AssetName, PositiveCoin.PositiveCoin>()
704+
const assetMap = new Map<AssetName.AssetName, bigint>()
648705
for (const [assetNameBytes, amount] of assetMapCddl.entries()) {
649706
const assetName = yield* ParseResult.decode(AssetName.FromBytes)(assetNameBytes)
650-
const positiveCoin = yield* ParseResult.decodeUnknown(Schema.typeSchema(PositiveCoin.PositiveCoinSchema))(amount)
651-
assetMap.set(assetName, positiveCoin)
707+
assetMap.set(assetName, amount)
652708
}
653709

654710
result.set(policyId, assetMap)

0 commit comments

Comments
 (0)