@alephium/web3 0.35.1 → 0.36.1

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.
@@ -20,7 +20,6 @@ import { Buffer } from 'buffer/'
20
20
  import fs from 'fs'
21
21
  import { promises as fsPromises } from 'fs'
22
22
  import {
23
- fromApiArray,
24
23
  fromApiNumber256,
25
24
  toApiNumber256,
26
25
  NamedVals,
@@ -32,9 +31,10 @@ import {
32
31
  Token,
33
32
  Val,
34
33
  fromApiTokens,
35
- fromApiVals,
36
- typeLength,
37
- getDefaultValue
34
+ getDefaultPrimitiveValue,
35
+ PrimitiveTypes,
36
+ decodeArrayType,
37
+ fromApiPrimitiveVal
38
38
  } from '../api'
39
39
  import { CompileProjectResult } from '../api/api-alephium'
40
40
  import {
@@ -84,7 +84,8 @@ enum SourceKind {
84
84
  Contract = 0,
85
85
  Script = 1,
86
86
  AbstractContract = 2,
87
- Interface = 3
87
+ Interface = 3,
88
+ Struct = 4
88
89
  }
89
90
 
90
91
  export type CompilerOptions = node.CompilerOptions & {
@@ -327,10 +328,45 @@ function removeOldArtifacts(dir: string) {
327
328
  }
328
329
  }
329
330
 
331
+ export class Struct {
332
+ name: string
333
+ fieldNames: string[]
334
+ fieldTypes: string[]
335
+ isMutable: boolean[]
336
+
337
+ constructor(name: string, fieldNames: string[], fieldTypes: string[], isMutable: boolean[]) {
338
+ this.name = name
339
+ this.fieldNames = fieldNames
340
+ this.fieldTypes = fieldTypes
341
+ this.isMutable = isMutable
342
+ }
343
+
344
+ static fromJson(json: any): Struct {
345
+ if (json.name === null || json.fieldNames === null || json.fieldTypes === null || json.isMutable === null) {
346
+ throw Error('The JSON for struct is incomplete')
347
+ }
348
+ return new Struct(json.name, json.fieldNames, json.fieldTypes, json.isMutable)
349
+ }
350
+
351
+ static fromStructSig(sig: node.StructSig): Struct {
352
+ return new Struct(sig.name, sig.fieldNames, sig.fieldTypes, sig.isMutable)
353
+ }
354
+
355
+ toJson(): any {
356
+ return {
357
+ name: this.name,
358
+ fieldNames: this.fieldNames,
359
+ fieldTypes: this.fieldTypes,
360
+ isMutable: this.isMutable
361
+ }
362
+ }
363
+ }
364
+
330
365
  export class Project {
331
366
  sourceInfos: SourceInfo[]
332
367
  contracts: Map<string, Compiled<Contract>>
333
368
  scripts: Map<string, Compiled<Script>>
369
+ structs: Struct[]
334
370
  projectArtifact: ProjectArtifact
335
371
 
336
372
  readonly contractsRootDir: string
@@ -346,11 +382,13 @@ export class Project {
346
382
  static readonly contractMatcher = new TypedMatcher('^Contract ([A-Z][a-zA-Z0-9]*)', SourceKind.Contract)
347
383
  static readonly interfaceMatcher = new TypedMatcher('^Interface ([A-Z][a-zA-Z0-9]*)', SourceKind.Interface)
348
384
  static readonly scriptMatcher = new TypedMatcher('^TxScript ([A-Z][a-zA-Z0-9]*)', SourceKind.Script)
385
+ static readonly structMatcher = new TypedMatcher('struct ([A-Z][a-zA-Z0-9]*)', SourceKind.Struct)
349
386
  static readonly matchers = [
350
387
  Project.abstractContractMatcher,
351
388
  Project.contractMatcher,
352
389
  Project.interfaceMatcher,
353
- Project.scriptMatcher
390
+ Project.scriptMatcher,
391
+ Project.structMatcher
354
392
  ]
355
393
 
356
394
  static buildProjectArtifact(
@@ -398,6 +436,7 @@ export class Project {
398
436
  sourceInfos: SourceInfo[],
399
437
  contracts: Map<string, Compiled<Contract>>,
400
438
  scripts: Map<string, Compiled<Script>>,
439
+ structs: Struct[],
401
440
  errorOnWarnings: boolean,
402
441
  projectArtifact: ProjectArtifact
403
442
  ) {
@@ -406,6 +445,7 @@ export class Project {
406
445
  this.sourceInfos = sourceInfos
407
446
  this.contracts = contracts
408
447
  this.scripts = scripts
448
+ this.structs = structs
409
449
  this.projectArtifact = projectArtifact
410
450
 
411
451
  if (errorOnWarnings) {
@@ -448,6 +488,24 @@ export class Project {
448
488
  return script.artifact
449
489
  }
450
490
 
491
+ private static async loadStructs(artifactsRootDir: string): Promise<Struct[]> {
492
+ const filePath = path.join(artifactsRootDir, 'structs.ral.json')
493
+ if (!fs.existsSync(filePath)) return []
494
+ const content = await fsPromises.readFile(filePath)
495
+ const json = JSON.parse(content.toString())
496
+ if (!Array.isArray(json)) {
497
+ throw Error(`Invalid structs JSON: ${content}`)
498
+ }
499
+ return Array.from(json).map((item) => Struct.fromJson(item))
500
+ }
501
+
502
+ private async saveStructsToFile(): Promise<void> {
503
+ if (this.structs.length === 0) return
504
+ const structs = this.structs.map((s) => s.toJson())
505
+ const filePath = path.join(this.artifactsRootDir, 'structs.ral.json')
506
+ return fsPromises.writeFile(filePath, JSON.stringify(structs, null, 2))
507
+ }
508
+
451
509
  private async saveArtifactsToFile(projectRootDir: string): Promise<void> {
452
510
  const artifactsRootDir = this.artifactsRootDir
453
511
  const saveToFile = async function (compiled: Compiled<Artifact>): Promise<void> {
@@ -460,6 +518,7 @@ export class Project {
460
518
  }
461
519
  this.contracts.forEach((contract) => saveToFile(contract))
462
520
  this.scripts.forEach((script) => saveToFile(script))
521
+ this.saveStructsToFile()
463
522
  await this.projectArtifact.saveToFile(projectRootDir)
464
523
  }
465
524
 
@@ -525,6 +584,7 @@ export class Project {
525
584
  const result = await Project.getCompileResult(provider, compilerOptions, removeDuplicates)
526
585
  const contracts = new Map<string, Compiled<Contract>>()
527
586
  const scripts = new Map<string, Compiled<Script>>()
587
+ const structs = result.structs === undefined ? [] : result.structs.map((item) => Struct.fromStructSig(item))
528
588
  result.contracts.forEach((contractResult) => {
529
589
  const sourceInfo = sourceInfos.find(
530
590
  (sourceInfo) => sourceInfo.type === SourceKind.Contract && sourceInfo.name === contractResult.name
@@ -533,7 +593,7 @@ export class Project {
533
593
  // this should never happen
534
594
  throw new Error(`SourceInfo does not exist for contract ${contractResult.name}`)
535
595
  }
536
- const contract = Contract.fromCompileResult(contractResult)
596
+ const contract = Contract.fromCompileResult(contractResult, structs)
537
597
  contracts.set(contract.name, new Compiled(sourceInfo, contract, contractResult.warnings))
538
598
  })
539
599
  result.scripts.forEach((scriptResult) => {
@@ -544,7 +604,7 @@ export class Project {
544
604
  // this should never happen
545
605
  throw new Error(`SourceInfo does not exist for script ${scriptResult.name}`)
546
606
  }
547
- const script = Script.fromCompileResult(scriptResult)
607
+ const script = Script.fromCompileResult(scriptResult, structs)
548
608
  scripts.set(script.name, new Compiled(sourceInfo, script, scriptResult.warnings))
549
609
  })
550
610
  const projectArtifact = Project.buildProjectArtifact(
@@ -560,6 +620,7 @@ export class Project {
560
620
  sourceInfos,
561
621
  contracts,
562
622
  scripts,
623
+ structs,
563
624
  errorOnWarnings,
564
625
  projectArtifact
565
626
  )
@@ -580,6 +641,7 @@ export class Project {
580
641
  try {
581
642
  const contracts = new Map<string, Compiled<Contract>>()
582
643
  const scripts = new Map<string, Compiled<Script>>()
644
+ const structs = await Project.loadStructs(artifactsRootDir)
583
645
  for (const sourceInfo of sourceInfos) {
584
646
  const info = projectArtifact.infos.get(sourceInfo.name)
585
647
  if (typeof info === 'undefined') {
@@ -588,10 +650,15 @@ export class Project {
588
650
  const warnings = info.warnings
589
651
  const artifactDir = sourceInfo.getArtifactPath(artifactsRootDir)
590
652
  if (sourceInfo.type === SourceKind.Contract) {
591
- const artifact = await Contract.fromArtifactFile(artifactDir, info.bytecodeDebugPatch, info.codeHashDebug)
653
+ const artifact = await Contract.fromArtifactFile(
654
+ artifactDir,
655
+ info.bytecodeDebugPatch,
656
+ info.codeHashDebug,
657
+ structs
658
+ )
592
659
  contracts.set(artifact.name, new Compiled(sourceInfo, artifact, warnings))
593
660
  } else if (sourceInfo.type === SourceKind.Script) {
594
- const artifact = await Script.fromArtifactFile(artifactDir, info.bytecodeDebugPatch)
661
+ const artifact = await Script.fromArtifactFile(artifactDir, info.bytecodeDebugPatch, structs)
595
662
  scripts.set(artifact.name, new Compiled(sourceInfo, artifact, warnings))
596
663
  }
597
664
  }
@@ -602,6 +669,7 @@ export class Project {
602
669
  sourceInfos,
603
670
  contracts,
604
671
  scripts,
672
+ structs,
605
673
  errorOnWarnings,
606
674
  projectArtifact
607
675
  )
@@ -821,6 +889,7 @@ export class Contract extends Artifact {
821
889
  readonly eventsSig: EventSig[]
822
890
  readonly constants: Constant[]
823
891
  readonly enums: Enum[]
892
+ readonly structs: Struct[]
824
893
  readonly stdInterfaceId?: HexString
825
894
 
826
895
  readonly bytecodeDebug: string
@@ -838,6 +907,7 @@ export class Contract extends Artifact {
838
907
  functions: FunctionSig[],
839
908
  constants: Constant[],
840
909
  enums: Enum[],
910
+ structs: Struct[],
841
911
  stdInterfaceId?: HexString
842
912
  ) {
843
913
  super(version, name, functions)
@@ -848,6 +918,7 @@ export class Contract extends Artifact {
848
918
  this.eventsSig = eventsSig
849
919
  this.constants = constants
850
920
  this.enums = enums
921
+ this.structs = structs
851
922
  this.stdInterfaceId = stdInterfaceId
852
923
 
853
924
  this.bytecodeDebug = ralph.buildDebugBytecode(this.bytecode, this.bytecodeDebugPatch)
@@ -855,7 +926,7 @@ export class Contract extends Artifact {
855
926
  }
856
927
 
857
928
  // TODO: safely parse json
858
- static fromJson(artifact: any, bytecodeDebugPatch = '', codeHashDebug = ''): Contract {
929
+ static fromJson(artifact: any, bytecodeDebugPatch = '', codeHashDebug = '', structs: Struct[] = []): Contract {
859
930
  if (
860
931
  artifact.version == null ||
861
932
  artifact.name == null ||
@@ -881,12 +952,13 @@ export class Contract extends Artifact {
881
952
  artifact.functions,
882
953
  artifact.constants,
883
954
  artifact.enums,
955
+ structs,
884
956
  artifact.stdInterfaceId === null ? undefined : artifact.stdInterfaceId
885
957
  )
886
958
  return contract
887
959
  }
888
960
 
889
- static fromCompileResult(result: node.CompileContractResult): Contract {
961
+ static fromCompileResult(result: node.CompileContractResult, structs: Struct[] = []): Contract {
890
962
  return new Contract(
891
963
  result.version,
892
964
  result.name,
@@ -899,15 +971,21 @@ export class Contract extends Artifact {
899
971
  result.functions,
900
972
  result.constants,
901
973
  result.enums,
974
+ structs,
902
975
  result.stdInterfaceId
903
976
  )
904
977
  }
905
978
 
906
979
  // support both 'code.ral' and 'code.ral.json'
907
- static async fromArtifactFile(path: string, bytecodeDebugPatch: string, codeHashDebug: string): Promise<Contract> {
980
+ static async fromArtifactFile(
981
+ path: string,
982
+ bytecodeDebugPatch: string,
983
+ codeHashDebug: string,
984
+ structs: Struct[] = []
985
+ ): Promise<Contract> {
908
986
  const content = await fsPromises.readFile(path)
909
987
  const artifact = JSON.parse(content.toString())
910
- return Contract.fromJson(artifact, bytecodeDebugPatch, codeHashDebug)
988
+ return Contract.fromJson(artifact, bytecodeDebugPatch, codeHashDebug, structs)
911
989
  }
912
990
 
913
991
  override toString(): string {
@@ -937,10 +1015,7 @@ export class Contract extends Artifact {
937
1015
  types: this.fieldsSig.types.slice(0, -1),
938
1016
  isMutable: this.fieldsSig.isMutable.slice(0, -1)
939
1017
  }
940
- return fields.names.reduce((acc, key, index) => {
941
- acc[`${key}`] = getDefaultValue(fields.types[`${index}`])
942
- return acc
943
- }, {})
1018
+ return getDefaultValue(fields, this.structs)
944
1019
  }
945
1020
 
946
1021
  toState<T extends Fields>(fields: T, asset: Asset, address?: string): ContractState<T> {
@@ -975,7 +1050,7 @@ export class Contract extends Artifact {
975
1050
  if (typeof fields === 'undefined') {
976
1051
  return []
977
1052
  } else {
978
- return toApiFields(fields, this.fieldsSig)
1053
+ return toApiFields(fields, this.fieldsSig, this.structs)
979
1054
  }
980
1055
  }
981
1056
 
@@ -986,7 +1061,7 @@ export class Contract extends Artifact {
986
1061
  throw new Error(`Invalid function name: ${funcName}`)
987
1062
  }
988
1063
 
989
- return toApiArgs(args, func)
1064
+ return toApiArgs(args, func, this.structs)
990
1065
  } else {
991
1066
  return []
992
1067
  }
@@ -997,14 +1072,22 @@ export class Contract extends Artifact {
997
1072
  }
998
1073
 
999
1074
  toApiContractStates(states?: ContractState[]): node.ContractState[] | undefined {
1000
- return typeof states != 'undefined' ? states.map((state) => toApiContractState(state)) : undefined
1075
+ return typeof states != 'undefined' ? states.map((state) => toApiContractState(state, this.structs)) : undefined
1001
1076
  }
1002
1077
 
1003
1078
  toApiTestContractParams(funcName: string, params: TestContractParams): node.TestContract {
1004
- const immFields =
1005
- params.initialFields === undefined ? [] : extractFields(params.initialFields, this.fieldsSig, false)
1006
- const mutFields =
1007
- params.initialFields === undefined ? [] : extractFields(params.initialFields, this.fieldsSig, true)
1079
+ const allFields =
1080
+ params.initialFields === undefined
1081
+ ? []
1082
+ : ralph.flattenFields(
1083
+ params.initialFields,
1084
+ this.fieldsSig.names,
1085
+ this.fieldsSig.types,
1086
+ this.fieldsSig.isMutable,
1087
+ this.structs
1088
+ )
1089
+ const immFields = allFields.filter((f) => !f.isMutable).map((f) => toApiVal(f.value, f.type))
1090
+ const mutFields = allFields.filter((f) => f.isMutable).map((f) => toApiVal(f.value, f.type))
1008
1091
  return {
1009
1092
  group: params.group,
1010
1093
  blockHash: params.blockHash,
@@ -1030,7 +1113,7 @@ export class Contract extends Artifact {
1030
1113
  bytecode: state.bytecode,
1031
1114
  initialStateHash: state.initialStateHash,
1032
1115
  codeHash: state.codeHash,
1033
- fields: fromApiFields(state.immFields, state.mutFields, this.fieldsSig),
1116
+ fields: fromApiFields(state.immFields, state.mutFields, this.fieldsSig, this.structs),
1034
1117
  fieldsSig: this.fieldsSig,
1035
1118
  asset: fromApiAsset(state.asset)
1036
1119
  }
@@ -1101,7 +1184,7 @@ export class Contract extends Artifact {
1101
1184
  ): TestContractResult<unknown> {
1102
1185
  const methodIndex = this.functions.findIndex((sig) => sig.name === methodName)
1103
1186
  const returnTypes = this.functions[`${methodIndex}`].returnTypes
1104
- const rawReturn = fromApiArray(result.returns, returnTypes)
1187
+ const rawReturn = fromApiArray(result.returns, returnTypes, this.structs)
1105
1188
  const returns = rawReturn.length === 0 ? null : rawReturn.length === 1 ? rawReturn[0] : rawReturn
1106
1189
 
1107
1190
  const addressToCodeHash = new Map<string, string>()
@@ -1143,7 +1226,12 @@ export class Contract extends Artifact {
1143
1226
 
1144
1227
  buildByteCodeToDeploy(initialFields: Fields, isDevnet: boolean): string {
1145
1228
  try {
1146
- return ralph.buildContractByteCode(isDevnet ? this.bytecodeDebug : this.bytecode, initialFields, this.fieldsSig)
1229
+ return ralph.buildContractByteCode(
1230
+ isDevnet ? this.bytecodeDebug : this.bytecode,
1231
+ initialFields,
1232
+ this.fieldsSig,
1233
+ this.structs
1234
+ )
1147
1235
  } catch (error) {
1148
1236
  throw new Error(`Failed to build bytecode for contract ${this.name}, error: ${error}`)
1149
1237
  }
@@ -1173,7 +1261,7 @@ export class Contract extends Artifact {
1173
1261
  methodIndex: number
1174
1262
  ): node.CallContract {
1175
1263
  const functionSig = this.functions[`${methodIndex}`]
1176
- const args = toApiVals(params.args ?? {}, functionSig.paramNames, functionSig.paramTypes)
1264
+ const args = toApiArgs(params.args ?? {}, functionSig, this.structs)
1177
1265
  return {
1178
1266
  ...params,
1179
1267
  group: groupIndex,
@@ -1191,7 +1279,7 @@ export class Contract extends Artifact {
1191
1279
  ): CallContractResult<unknown> {
1192
1280
  const returnTypes = this.functions[`${methodIndex}`].returnTypes
1193
1281
  const callResult = tryGetCallResult(result)
1194
- const rawReturn = fromApiArray(callResult.returns, returnTypes)
1282
+ const rawReturn = fromApiArray(callResult.returns, returnTypes, this.structs)
1195
1283
  const returns = rawReturn.length === 0 ? null : rawReturn.length === 1 ? rawReturn[0] : rawReturn
1196
1284
 
1197
1285
  const addressToCodeHash = new Map<string, string>()
@@ -1212,6 +1300,7 @@ export class Script extends Artifact {
1212
1300
  readonly bytecodeTemplate: string
1213
1301
  readonly bytecodeDebugPatch: string
1214
1302
  readonly fieldsSig: FieldsSig
1303
+ readonly structs: Struct[]
1215
1304
 
1216
1305
  constructor(
1217
1306
  version: string,
@@ -1219,27 +1308,30 @@ export class Script extends Artifact {
1219
1308
  bytecodeTemplate: string,
1220
1309
  bytecodeDebugPatch: string,
1221
1310
  fieldsSig: FieldsSig,
1222
- functions: FunctionSig[]
1311
+ functions: FunctionSig[],
1312
+ structs: Struct[]
1223
1313
  ) {
1224
1314
  super(version, name, functions)
1225
1315
  this.bytecodeTemplate = bytecodeTemplate
1226
1316
  this.bytecodeDebugPatch = bytecodeDebugPatch
1227
1317
  this.fieldsSig = fieldsSig
1318
+ this.structs = structs
1228
1319
  }
1229
1320
 
1230
- static fromCompileResult(result: node.CompileScriptResult): Script {
1321
+ static fromCompileResult(result: node.CompileScriptResult, structs: Struct[] = []): Script {
1231
1322
  return new Script(
1232
1323
  result.version,
1233
1324
  result.name,
1234
1325
  result.bytecodeTemplate,
1235
1326
  result.bytecodeDebugPatch,
1236
1327
  result.fields,
1237
- result.functions
1328
+ result.functions,
1329
+ structs
1238
1330
  )
1239
1331
  }
1240
1332
 
1241
1333
  // TODO: safely parse json
1242
- static fromJson(artifact: any, bytecodeDebugPatch = ''): Script {
1334
+ static fromJson(artifact: any, bytecodeDebugPatch = '', structs: Struct[] = []): Script {
1243
1335
  if (
1244
1336
  artifact.version == null ||
1245
1337
  artifact.name == null ||
@@ -1255,14 +1347,15 @@ export class Script extends Artifact {
1255
1347
  artifact.bytecodeTemplate,
1256
1348
  bytecodeDebugPatch,
1257
1349
  artifact.fieldsSig,
1258
- artifact.functions
1350
+ artifact.functions,
1351
+ structs
1259
1352
  )
1260
1353
  }
1261
1354
 
1262
- static async fromArtifactFile(path: string, bytecodeDebugPatch: string): Promise<Script> {
1355
+ static async fromArtifactFile(path: string, bytecodeDebugPatch: string, structs: Struct[] = []): Promise<Script> {
1263
1356
  const content = await fsPromises.readFile(path)
1264
1357
  const artifact = JSON.parse(content.toString())
1265
- return this.fromJson(artifact, bytecodeDebugPatch)
1358
+ return this.fromJson(artifact, bytecodeDebugPatch, structs)
1266
1359
  }
1267
1360
 
1268
1361
  override toString(): string {
@@ -1295,34 +1388,86 @@ export class Script extends Artifact {
1295
1388
 
1296
1389
  buildByteCodeToDeploy(initialFields: Fields): string {
1297
1390
  try {
1298
- return ralph.buildScriptByteCode(this.bytecodeTemplate, initialFields, this.fieldsSig)
1391
+ return ralph.buildScriptByteCode(this.bytecodeTemplate, initialFields, this.fieldsSig, this.structs)
1299
1392
  } catch (error) {
1300
1393
  throw new Error(`Failed to build bytecode for script ${this.name}, error: ${error}`)
1301
1394
  }
1302
1395
  }
1303
1396
  }
1304
1397
 
1305
- function fromApiFields(immFields: node.Val[], mutFields: node.Val[], fieldsSig: node.FieldsSig): Fields {
1306
- const vals: node.Val[] = []
1307
- let immIndex = 0
1308
- let mutIndex = 0
1309
- const isMutable = fieldsSig.types.flatMap((tpe, index) =>
1310
- Array(typeLength(tpe)).fill(fieldsSig.isMutable[`${index}`])
1311
- )
1312
- isMutable.forEach((mutable) => {
1313
- if (mutable) {
1314
- vals.push(mutFields[`${mutIndex}`])
1315
- mutIndex += 1
1316
- } else {
1317
- vals.push(immFields[`${immIndex}`])
1318
- immIndex += 1
1319
- }
1320
- })
1321
- return fromApiVals(vals, fieldsSig.names, fieldsSig.types)
1398
+ export function fromApiFields(
1399
+ immFields: node.Val[],
1400
+ mutFields: node.Val[],
1401
+ fieldsSig: FieldsSig,
1402
+ structs: Struct[]
1403
+ ): NamedVals {
1404
+ let [immIndex, mutIndex] = [0, 0]
1405
+ const func = (type: string, isMutable: boolean): Val => {
1406
+ const nodeVal = isMutable ? mutFields[mutIndex++] : immFields[immIndex++]
1407
+ return fromApiPrimitiveVal(nodeVal, type)
1408
+ }
1409
+
1410
+ return fieldsSig.names.reduce((acc, name, index) => {
1411
+ const fieldType = fieldsSig.types[`${index}`]
1412
+ const isMutable = fieldsSig.isMutable[`${index}`]
1413
+ acc[`${name}`] = buildVal(isMutable, fieldType, structs, func)
1414
+ return acc
1415
+ }, {})
1416
+ }
1417
+
1418
+ function buildVal(
1419
+ isMutable: boolean,
1420
+ type: string,
1421
+ structs: Struct[],
1422
+ func: (primitiveType: string, isMutable: boolean) => Val
1423
+ ): Val {
1424
+ if (type.startsWith('[')) {
1425
+ const [baseType, size] = decodeArrayType(type)
1426
+ return Array.from(Array(size).keys()).map(() => buildVal(isMutable, baseType, structs, func))
1427
+ }
1428
+ const struct = structs.find((s) => s.name === type)
1429
+ if (struct !== undefined) {
1430
+ return struct.fieldNames.reduce((acc, name, index) => {
1431
+ const fieldType = struct.fieldTypes[`${index}`]
1432
+ const isFieldMutable = isMutable && struct.isMutable[`${index}`]
1433
+ acc[`${name}`] = buildVal(isFieldMutable, fieldType, structs, func)
1434
+ return acc
1435
+ }, {})
1436
+ }
1437
+ const primitiveType = PrimitiveTypes.includes(type) ? type : 'ByteVec' // contract type
1438
+ return func(primitiveType, isMutable)
1439
+ }
1440
+
1441
+ export function getDefaultValue(fieldsSig: FieldsSig, structs: Struct[]): Fields {
1442
+ return fieldsSig.names.reduce((acc, name, index) => {
1443
+ const type = fieldsSig.types[`${index}`]
1444
+ acc[`${name}`] = buildVal(false, type, structs, getDefaultPrimitiveValue)
1445
+ return acc
1446
+ }, {})
1447
+ }
1448
+
1449
+ function fromApiVal(iter: IterableIterator<node.Val>, type: string, structs: Struct[], systemEvent = false): Val {
1450
+ const func = (primitiveType: string): Val => {
1451
+ const currentValue = iter.next()
1452
+ if (currentValue.done) throw Error('Not enough vals')
1453
+ return fromApiPrimitiveVal(currentValue.value, primitiveType, systemEvent)
1454
+ }
1455
+ return buildVal(false, type, structs, func)
1456
+ }
1457
+
1458
+ export function fromApiArray(values: node.Val[], types: string[], structs: Struct[]): Val[] {
1459
+ const iter = values.values()
1460
+ return types.map((type) => fromApiVal(iter, type, structs))
1322
1461
  }
1323
1462
 
1324
- function fromApiEventFields(vals: node.Val[], eventSig: node.EventSig, systemEvent = false): Fields {
1325
- return fromApiVals(vals, eventSig.fieldNames, eventSig.fieldTypes, systemEvent)
1463
+ export function fromApiEventFields(vals: node.Val[], eventSig: node.EventSig, systemEvent = false): Fields {
1464
+ const iter = vals.values()
1465
+ return eventSig.fieldNames.reduce((acc, name, index) => {
1466
+ const type = eventSig.fieldTypes[`${index}`]
1467
+ // currently event does not support struct type
1468
+ acc[`${name}`] = fromApiVal(iter, type, [], systemEvent)
1469
+ return acc
1470
+ }, {})
1326
1471
  }
1327
1472
 
1328
1473
  export interface Asset {
@@ -1360,50 +1505,33 @@ export interface ContractState<T extends Fields = Fields> {
1360
1505
  asset: Asset
1361
1506
  }
1362
1507
 
1363
- function getVal(vals: NamedVals, name: string): Val {
1364
- if (name in vals) {
1365
- return vals[`${name}`]
1366
- } else {
1367
- throw Error(`No Val exists for ${name}`)
1368
- }
1369
- }
1370
-
1371
- function extractFields(fields: NamedVals, fieldsSig: FieldsSig, mutable: boolean) {
1372
- const fieldIndexes = fieldsSig.names
1373
- .map((_, index) => index)
1374
- .filter((index) => fieldsSig.isMutable[`${index}`] === mutable)
1375
- const fieldNames = fieldIndexes.map((index) => fieldsSig.names[`${index}`])
1376
- const fieldTypes = fieldIndexes.map((index) => fieldsSig.types[`${index}`])
1377
- return toApiVals(fields, fieldNames, fieldTypes)
1378
- }
1379
-
1380
- function toApiContractState(state: ContractState): node.ContractState {
1508
+ function toApiContractState(state: ContractState, structs: Struct[]): node.ContractState {
1381
1509
  const stateFields = state.fields ?? {}
1510
+ const fieldsSig = state.fieldsSig
1511
+ const allFields = ralph.flattenFields(stateFields, fieldsSig.names, fieldsSig.types, fieldsSig.isMutable, structs)
1512
+ const immFields = allFields.filter((f) => !f.isMutable).map((f) => toApiVal(f.value, f.type))
1513
+ const mutFields = allFields.filter((f) => f.isMutable).map((f) => toApiVal(f.value, f.type))
1382
1514
  return {
1383
1515
  address: state.address,
1384
1516
  bytecode: state.bytecode,
1385
1517
  codeHash: state.codeHash,
1386
1518
  initialStateHash: state.initialStateHash,
1387
- immFields: extractFields(stateFields, state.fieldsSig, false),
1388
- mutFields: extractFields(stateFields, state.fieldsSig, true),
1519
+ immFields,
1520
+ mutFields,
1389
1521
  asset: toApiAsset(state.asset)
1390
1522
  }
1391
1523
  }
1392
1524
 
1393
- function toApiFields(fields: Fields, fieldsSig: FieldsSig): node.Val[] {
1394
- return toApiVals(fields, fieldsSig.names, fieldsSig.types)
1525
+ function toApiFields(fields: Fields, fieldsSig: FieldsSig, structs: Struct[]): node.Val[] {
1526
+ return ralph
1527
+ .flattenFields(fields, fieldsSig.names, fieldsSig.types, fieldsSig.isMutable, structs)
1528
+ .map((f) => toApiVal(f.value, f.type))
1395
1529
  }
1396
1530
 
1397
- function toApiArgs(args: Arguments, funcSig: FunctionSig): node.Val[] {
1398
- return toApiVals(args, funcSig.paramNames, funcSig.paramTypes)
1399
- }
1400
-
1401
- export function toApiVals(fields: Fields, names: string[], types: string[]): node.Val[] {
1402
- return names.map((name, index) => {
1403
- const val = getVal(fields, name)
1404
- const tpe = types[`${index}`]
1405
- return toApiVal(val, tpe)
1406
- })
1531
+ function toApiArgs(args: Arguments, funcSig: FunctionSig, structs: Struct[]): node.Val[] {
1532
+ return ralph
1533
+ .flattenFields(args, funcSig.paramNames, funcSig.paramTypes, funcSig.paramIsMutable, structs)
1534
+ .map((f) => toApiVal(f.value, f.type))
1407
1535
  }
1408
1536
 
1409
1537
  function toApiInputAsset(inputAsset: InputAsset): node.TestInputAsset {
@@ -1783,9 +1911,7 @@ export function decodeEvent<F extends Fields, M extends ContractEvent<F>>(
1783
1911
  throw new Error('Invalid event index: ' + event.eventIndex + ', expected: ' + targetEventIndex)
1784
1912
  }
1785
1913
  const eventSig = contract.eventsSig[`${targetEventIndex}`]
1786
- const fieldNames = eventSig.fieldNames
1787
- const fieldTypes = eventSig.fieldTypes
1788
- const fields = fromApiVals(event.fields, fieldNames, fieldTypes)
1914
+ const fields = fromApiEventFields(event.fields, eventSig)
1789
1915
  return {
1790
1916
  contractAddress: instance.address,
1791
1917
  blockHash: event.blockHash,