@helios-lang/contract-utils 0.2.9 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helios-lang/contract-utils",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Convenience and type-safety utilities for using Helios validators from within Typescript",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -39,7 +39,7 @@
39
39
  "@helios-lang/codec-utils": "^0.1.30",
40
40
  "@helios-lang/compiler-utils": "^0.1.25",
41
41
  "@helios-lang/ledger": "^0.2.7",
42
- "@helios-lang/type-utils": "^0.1.19",
42
+ "@helios-lang/type-utils": "^0.1.20",
43
43
  "@helios-lang/uplc": "^0.1.31"
44
44
  }
45
45
  }
package/src/cast/Cast.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  ValidatorHash,
18
18
  Value
19
19
  } from "@helios-lang/ledger"
20
- import { None } from "@helios-lang/type-utils"
20
+ import { None, expectSome } from "@helios-lang/type-utils"
21
21
  import {
22
22
  ByteArrayData,
23
23
  ConstrData,
@@ -102,120 +102,156 @@ export class Cast {
102
102
  /**
103
103
  * @param {TypeSchema} schema
104
104
  * @param {any} x
105
+ * @param {Record<string, TypeSchema>} defs
105
106
  * @returns {UplcData}
106
107
  */
107
- function schemaToUplc(schema, x) {
108
- if ("primitiveType" in schema) {
109
- switch (schema.primitiveType) {
110
- case "Address":
111
- return Address.new(x).toUplcData()
112
- case "Any":
113
- return new IntData(0n)
114
- case "AssetClass":
115
- return AssetClass.new(x).toUplcData()
116
- case "Bool":
117
- return encodeBoolData(x)
118
- case "ByteArray":
119
- return new ByteArrayData(x)
120
- case "Credential":
121
- case "PaymentCredential":
122
- return SpendingCredential.new(x).toUplcData()
123
- case "Data":
124
- return x
125
- case "DatumHash":
126
- return DatumHash.new(x).toUplcData()
127
- case "DCert":
128
- return x.toUplcData()
129
- case "Duration":
130
- case "Int":
131
- return new IntData(x)
132
- case "MintingPolicyHash":
133
- return MintingPolicyHash.new(x).toUplcData()
134
- case "PubKeyHash":
135
- return PubKeyHash.new(x).toUplcData()
136
- case "Real":
137
- return encodeRealData(x)
138
- case "ScriptHash":
139
- return ScriptHash.new(x).toUplcData()
140
- case "StakingCredential":
141
- return StakingCredential.new(x).toUplcData()
142
- case "StakingHash":
143
- return StakingHash.new(x).toUplcData()
144
- case "String":
145
- return new ByteArrayData(encodeUtf8(x))
146
- case "Time":
147
- return new IntData(x)
148
- case "TimeRange":
149
- return TimeRange.new(x).toUplcData()
150
- case "TxId":
151
- return TxId.new(x).toUplcData()
152
- case "TxOutputDatum":
153
- return x
154
- ? TxOutputDatum.new(x)?.toUplcData() ??
155
- new ConstrData(0, [])
156
- : new ConstrData(0, [])
157
- case "TxOutputId":
158
- return TxOutputId.new(x).toUplcData()
159
- case "ValidatorHash":
160
- return ValidatorHash.new(x).toUplcData()
161
- case "Value":
162
- return Value.new(x).toUplcData()
163
- default:
164
- throw new Error(
165
- `not yet implemented for ${schema.primitiveType}`
166
- )
108
+ function schemaToUplc(schema, x, defs = {}) {
109
+ const kind = schema.kind
110
+
111
+ switch (kind) {
112
+ case "reference": {
113
+ const def = expectSome(defs[schema.id])
114
+ return schemaToUplc(def, x, defs)
167
115
  }
168
- } else if ("listItemType" in schema) {
169
- return new ListData(x.map((x) => schemaToUplc(schema.listItemType, x)))
170
- } else if ("mapKeyType" in schema) {
171
- const entries = x instanceof Map ? x.entries() : x
172
- return new MapData(
173
- entries.map(([k, v]) => [
174
- schemaToUplc(schema.mapKeyType, k),
175
- schemaToUplc(schema.mapValueType, v)
176
- ])
177
- )
178
- } else if ("optionSomeType" in schema) {
179
- return encodeOptionData(
180
- x ? schemaToUplc(schema.optionSomeType, x) : None
181
- )
182
- } else if ("structFieldTypes" in schema) {
183
- // TODO: Cip-68 support
184
- if (schema.structFieldTypes.length == 1) {
185
- return schemaToUplc(
186
- schema.structFieldTypes[0].type,
187
- x[schema.structFieldTypes[0].name]
188
- )
189
- } else {
116
+ case "internal": {
117
+ const name = schema.name
118
+
119
+ switch (name) {
120
+ case "Address":
121
+ return Address.new(x).toUplcData()
122
+ case "Any":
123
+ return new IntData(0n)
124
+ case "AssetClass":
125
+ return AssetClass.new(x).toUplcData()
126
+ case "Bool":
127
+ return encodeBoolData(x)
128
+ case "ByteArray":
129
+ return new ByteArrayData(x)
130
+ case "Credential":
131
+ case "SpendingCredential":
132
+ return SpendingCredential.new(x).toUplcData()
133
+ case "Data":
134
+ return x
135
+ case "DatumHash":
136
+ return DatumHash.new(x).toUplcData()
137
+ case "DCert":
138
+ return x.toUplcData()
139
+ case "Duration":
140
+ case "Int":
141
+ return new IntData(x)
142
+ case "MintingPolicyHash":
143
+ return MintingPolicyHash.new(x).toUplcData()
144
+ case "PubKeyHash":
145
+ return PubKeyHash.new(x).toUplcData()
146
+ case "Real":
147
+ return encodeRealData(x)
148
+ case "Ratio":
149
+ return new ListData([new IntData(x[0]), new IntData(x[1])])
150
+ case "ScriptHash":
151
+ return ScriptHash.new(x).toUplcData()
152
+ case "StakingCredential":
153
+ return StakingCredential.new(x).toUplcData()
154
+ case "StakingHash":
155
+ return StakingHash.new(x).toUplcData()
156
+ case "String":
157
+ return new ByteArrayData(encodeUtf8(x))
158
+ case "Time":
159
+ return new IntData(x)
160
+ case "TimeRange":
161
+ return TimeRange.new(x).toUplcData()
162
+ case "TxId":
163
+ return TxId.new(x).toUplcData()
164
+ case "TxOutputDatum":
165
+ return x
166
+ ? TxOutputDatum.new(x)?.toUplcData() ??
167
+ new ConstrData(0, [])
168
+ : new ConstrData(0, [])
169
+ case "TxOutputId":
170
+ return TxOutputId.new(x).toUplcData()
171
+ case "ValidatorHash":
172
+ return ValidatorHash.new(x).toUplcData()
173
+ case "Value":
174
+ return Value.new(x).toUplcData()
175
+ default:
176
+ throw new Error(`not yet implemented for ${name}`)
177
+ }
178
+ }
179
+ case "list":
190
180
  return new ListData(
191
- schema.structFieldTypes.map(({ name, type }) =>
192
- schemaToUplc(type, x[name])
193
- )
181
+ x.map((x) => schemaToUplc(schema.itemType, x, defs))
182
+ )
183
+ case "map": {
184
+ const entries = x instanceof Map ? x.entries() : x
185
+ return new MapData(
186
+ entries.map(([k, v]) => [
187
+ schemaToUplc(schema.keyType, k, defs),
188
+ schemaToUplc(schema.valueType, v, defs)
189
+ ])
194
190
  )
195
191
  }
196
- } else if ("enumVariantTypes") {
197
- const variantName = Object.keys(x)[0]
198
- const variantFields = Object.values(x)[0]
199
- const tag = schema.enumVariantTypes.findIndex(
200
- (v) => v.name == variantName
201
- )
202
-
203
- if (tag == -1) {
204
- throw new Error(
205
- `invalid variant ${variantName} (expected: ${schema.enumVariantTypes.map((v) => v.name).join(", ")})`
192
+ case "option":
193
+ return encodeOptionData(
194
+ x ? schemaToUplc(schema.someType, x, defs) : None
206
195
  )
196
+ case "struct": {
197
+ defs[schema.id] = schema
198
+ switch (schema.format) {
199
+ case "singleton":
200
+ return schemaToUplc(
201
+ schema.fieldTypes[0].type,
202
+ x[schema.fieldTypes[0].name],
203
+ defs
204
+ )
205
+ case "list":
206
+ return new ListData(
207
+ schema.fieldTypes.map(({ name, type }) =>
208
+ schemaToUplc(type, x[name])
209
+ )
210
+ )
211
+ case "map":
212
+ return new MapData(
213
+ schema.fieldTypes.map(({ name, type }) => [
214
+ new ByteArrayData(encodeUtf8(name)),
215
+ schemaToUplc(type, x[name], defs)
216
+ ])
217
+ )
218
+ default:
219
+ throw new Error(
220
+ `unhandled struct format '${schema.format}'`
221
+ )
222
+ }
207
223
  }
224
+ case "enum": {
225
+ defs[schema.id] = schema
226
+ const variantName = Object.keys(x)[0]
227
+ const variantFields = Object.values(x)[0]
228
+ const tag = schema.variantTypes.findIndex(
229
+ (v) => v.name == variantName
230
+ )
231
+
232
+ if (tag == -1) {
233
+ throw new Error(
234
+ `invalid variant ${variantName} (expected: ${schema.variantTypes.map((v) => v.name).join(", ")})`
235
+ )
236
+ }
208
237
 
209
- return new ConstrData(
210
- tag,
211
- schema.enumVariantTypes[tag].fieldTypes.map((f) =>
212
- schemaToUplc(f.type, variantFields[f.name])
238
+ return new ConstrData(
239
+ tag,
240
+ schema.variantTypes[tag].fieldTypes.map((f) =>
241
+ schemaToUplc(f.type, variantFields[f.name], defs)
242
+ )
213
243
  )
214
- )
215
- } else {
216
- throw new Error(
217
- `not yet implemented for ${JSON.stringify(schema, undefined, 4)}`
218
- )
244
+ }
245
+ case "variant":
246
+ defs[schema.id] = schema
247
+ return new ConstrData(
248
+ schema.tag,
249
+ schema.fieldTypes.map(({ name, type }) =>
250
+ schemaToUplc(type, x[name])
251
+ )
252
+ )
253
+ default:
254
+ throw new Error(`unhandled schema kind '${kind}'`)
219
255
  }
220
256
  }
221
257
 
@@ -224,137 +260,205 @@ function schemaToUplc(schema, x) {
224
260
  * @param {TypeSchema} schema
225
261
  * @param {UplcData} data
226
262
  * @param {CastConfig} config
263
+ * @param {Record<string, TypeSchema>} defs
227
264
  * @returns {any}
228
265
  */
229
- function uplcToSchema(schema, data, config) {
230
- if ("primitiveType" in schema) {
231
- switch (schema.primitiveType) {
232
- case "Address":
233
- return Address.fromUplcData(config.isMainnet, data)
234
- case "Any":
235
- // TODO: should this throw an error?
236
- return null
237
- case "Bool":
238
- return decodeBoolData(data, true)
239
- case "ByteArray":
240
- return ByteArrayData.expect(data).bytes
241
- case "SpendingCredential":
242
- case "Credential":
243
- return SpendingCredential.fromUplcData(data)
244
- case "Data":
245
- return data
246
- case "DCert":
247
- throw new Error(
248
- "can't convert UplcData back into DCert (significant loss of information)"
249
- )
250
- case "Duration":
251
- case "Int":
252
- return IntData.expect(data).value
253
- case "MintingPolicyHash":
254
- return MintingPolicyHash.fromUplcData(data)
255
- case "PubKeyHash":
256
- return PubKeyHash.fromUplcData(data)
257
- case "Real":
258
- return decodeRealData(data)
259
- case "ScriptHash":
260
- return ScriptHash.fromUplcData(data)
261
- case "StakingCredential":
262
- return StakingCredential.fromUplcData(data)
263
- case "StakingHash":
264
- return StakingHash.fromUplcData(data)
265
- case "StakingValidatorHash":
266
- return StakingValidatorHash.fromUplcData(data)
267
- case "String":
268
- return decodeUtf8(ByteArrayData.expect(data).bytes)
269
- case "Time":
270
- return Number(IntData.expect(data).value) // a number has enough precision to represent ms since 1970 for another 142000 years
271
- case "TimeRange":
272
- return TimeRange.fromUplcData(data)
273
- case "TxId":
274
- return TxId.fromUplcData(data)
275
- case "TxOutputId":
276
- return TxOutputId.fromUplcData(data)
277
- case "ValidatorHash":
278
- return ValidatorHash.fromUplcData(data)
279
- case "Value":
280
- return Value.fromUplcData(data)
281
- default:
282
- throw new Error(
283
- `not yet implemented for ${schema.primitiveType}`
284
- )
285
- }
286
- } else if ("listItemType" in schema) {
287
- return ListData.expect(data).items.map((x) =>
288
- uplcToSchema(schema.listItemType, x, config)
289
- )
290
- } else if ("mapKeyType" in schema) {
291
- return new Map(
292
- MapData.expect(data).items.map(([k, v]) => [
293
- uplcToSchema(schema.mapKeyType, k, config),
294
- uplcToSchema(schema.mapValueType, v, config)
295
- ])
296
- )
297
- } else if ("optionSomeType" in schema) {
298
- const optionData = decodeOptionData(data)
299
- return optionData
300
- ? uplcToSchema(schema.optionSomeType, optionData, config)
301
- : None
302
- } else if ("structFieldTypes" in schema) {
303
- const nExpected = schema.structFieldTypes.length
304
- // TODO: Cip-68 support
305
- if (nExpected == 1) {
306
- return {
307
- [schema.structFieldTypes[0].name]: uplcToSchema(
308
- schema.structFieldTypes[0].type,
309
- data,
310
- config
311
- )
312
- }
313
- } else {
314
- const fields = ListData.expect(data).items
266
+ function uplcToSchema(schema, data, config, defs = {}) {
267
+ const kind = schema.kind
315
268
 
316
- if (fields.length != nExpected) {
317
- throw new Error(
318
- `expected ${nExpected} fields in struct, got ${fields.length} fields`
319
- )
320
- }
269
+ switch (kind) {
270
+ case "internal": {
271
+ const name = schema.name
321
272
 
322
- return Object.fromEntries(
323
- fields.map((field, i) => [
324
- schema.structFieldTypes[i].name,
325
- uplcToSchema(schema.structFieldTypes[i].type, field, config)
273
+ switch (name) {
274
+ case "Address":
275
+ return Address.fromUplcData(config.isMainnet, data)
276
+ case "Any":
277
+ // TODO: should this throw an error?
278
+ return null
279
+ case "Bool":
280
+ return decodeBoolData(data, true)
281
+ case "ByteArray":
282
+ return ByteArrayData.expect(data).bytes
283
+ case "SpendingCredential":
284
+ case "Credential":
285
+ return SpendingCredential.fromUplcData(data)
286
+ case "Data":
287
+ return data
288
+ case "DCert":
289
+ throw new Error(
290
+ "can't convert UplcData back into DCert (significant loss of information)"
291
+ )
292
+ case "Duration":
293
+ case "Int":
294
+ return IntData.expect(data).value
295
+ case "MintingPolicyHash":
296
+ return MintingPolicyHash.fromUplcData(data)
297
+ case "PubKeyHash":
298
+ return PubKeyHash.fromUplcData(data)
299
+ case "Ratio": {
300
+ const [top, bottom] = ListData.expect(data).items
301
+ return [
302
+ IntData.expect(top).value,
303
+ IntData.expect(bottom).value
304
+ ]
305
+ }
306
+ case "Real":
307
+ return decodeRealData(data)
308
+ case "ScriptHash":
309
+ return ScriptHash.fromUplcData(data)
310
+ case "StakingCredential":
311
+ return StakingCredential.fromUplcData(data)
312
+ case "StakingHash":
313
+ return StakingHash.fromUplcData(data)
314
+ case "StakingValidatorHash":
315
+ return StakingValidatorHash.fromUplcData(data)
316
+ case "String":
317
+ return decodeUtf8(ByteArrayData.expect(data).bytes)
318
+ case "Time":
319
+ return Number(IntData.expect(data).value) // a number has enough precision to represent ms since 1970 for another 142000 years
320
+ case "TimeRange":
321
+ return TimeRange.fromUplcData(data)
322
+ case "TxId":
323
+ return TxId.fromUplcData(data)
324
+ case "TxOutputId":
325
+ return TxOutputId.fromUplcData(data)
326
+ case "ValidatorHash":
327
+ return ValidatorHash.fromUplcData(data)
328
+ case "Value":
329
+ return Value.fromUplcData(data)
330
+ default:
331
+ throw new Error(`not yet implemented for ${name}`)
332
+ }
333
+ }
334
+ case "list":
335
+ return ListData.expect(data).items.map((x) =>
336
+ uplcToSchema(schema.itemType, x, config, defs)
337
+ )
338
+ case "map":
339
+ return new Map(
340
+ MapData.expect(data).items.map(([k, v]) => [
341
+ uplcToSchema(schema.keyType, k, config, defs),
342
+ uplcToSchema(schema.valueType, v, config, defs)
326
343
  ])
327
344
  )
345
+ case "option": {
346
+ const optionData = decodeOptionData(data)
347
+ return optionData
348
+ ? uplcToSchema(schema.someType, optionData, config, defs)
349
+ : None
328
350
  }
329
- } else if ("enumVariantTypes" in schema) {
330
- const { tag, fields } = ConstrData.expect(data)
351
+ case "struct": {
352
+ defs[schema.id] = schema
353
+ switch (schema.format) {
354
+ case "singleton":
355
+ return {
356
+ [schema.fieldTypes[0].name]: uplcToSchema(
357
+ schema.fieldTypes[0].type,
358
+ data,
359
+ config,
360
+ defs
361
+ )
362
+ }
363
+ case "list": {
364
+ const fields = ListData.expect(data).items
365
+
366
+ if (fields.length != schema.fieldTypes.length) {
367
+ throw new Error(
368
+ `expected ${schema.fieldTypes.length} fields in struct, got ${fields.length} fields`
369
+ )
370
+ }
331
371
 
332
- const variantSchema = schema.enumVariantTypes[tag]
372
+ return Object.fromEntries(
373
+ fields.map((field, i) => [
374
+ schema.fieldTypes[i].name,
375
+ uplcToSchema(
376
+ schema.fieldTypes[i].type,
377
+ field,
378
+ config,
379
+ defs
380
+ )
381
+ ])
382
+ )
383
+ }
384
+ case "map": {
385
+ const entries = MapData.expect(data).items
333
386
 
334
- if (!variantSchema) {
335
- throw new Error(`tag ${tag} out of range`)
387
+ if (entries.length != schema.fieldTypes.length) {
388
+ throw new Error(
389
+ `expected ${schema.fieldTypes.length} fields in struct, got ${entries.length} fields`
390
+ )
391
+ }
392
+
393
+ return new Map(
394
+ schema.fieldTypes.map(({ name, type }) => {
395
+ const pair = expectSome(
396
+ entries.find(
397
+ ([key, _]) =>
398
+ decodeUtf8(
399
+ ByteArrayData.expect(key).bytes
400
+ ) == name
401
+ )
402
+ )
403
+
404
+ return [
405
+ name,
406
+ uplcToSchema(type, pair[1], config, defs)
407
+ ]
408
+ })
409
+ )
410
+ }
411
+ default:
412
+ throw new Error(
413
+ `unhandled struct format '${schema.format}'`
414
+ )
415
+ }
336
416
  }
417
+ case "enum": {
418
+ defs[schema.id] = schema
419
+ const { tag, fields } = ConstrData.expect(data)
337
420
 
338
- const nExpected = variantSchema.fieldTypes.length
421
+ const variantSchema = schema.variantTypes[tag]
339
422
 
340
- if (fields.length != nExpected) {
341
- throw new Error(
342
- `expected ${nExpected} fields for variant ${variantSchema.name} (tag ${tag}), got ${fields.length} fields`
343
- )
423
+ if (!variantSchema) {
424
+ throw new Error(`tag ${tag} out of range`)
425
+ }
426
+
427
+ const nExpected = variantSchema.fieldTypes.length
428
+
429
+ if (fields.length != nExpected) {
430
+ throw new Error(
431
+ `expected ${nExpected} fields for variant ${variantSchema.name} (tag ${tag}), got ${fields.length} fields`
432
+ )
433
+ }
434
+
435
+ return {
436
+ [variantSchema.name]: Object.fromEntries(
437
+ fields.map((f, i) => [
438
+ variantSchema.fieldTypes[i].name,
439
+ uplcToSchema(
440
+ variantSchema.fieldTypes[i].type,
441
+ f,
442
+ config,
443
+ defs
444
+ )
445
+ ])
446
+ )
447
+ }
344
448
  }
449
+ case "variant": {
450
+ defs[schema.id] = schema
451
+ const { fields } = ConstrData.expect(data)
345
452
 
346
- return {
347
- [variantSchema.name]: Object.fromEntries(
348
- fields.map((f, i) => [
349
- variantSchema.fieldTypes[i].name,
350
- uplcToSchema(variantSchema.fieldTypes[i].type, f, config)
453
+ return Object.fromEntries(
454
+ fields.map((field, i) => [
455
+ schema.fieldTypes[i].name,
456
+ uplcToSchema(schema.fieldTypes[i].type, field, config, defs)
351
457
  ])
352
458
  )
353
459
  }
354
- } else {
355
- throw new Error(
356
- `not yet implemented for ${JSON.stringify(schema, undefined, 4)}`
357
- )
460
+ default:
461
+ throw new Error(`unhandled schema kind '${kind}'`)
358
462
  }
359
463
  }
360
464