@alephium/web3 0.37.0 → 0.38.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.
Files changed (97) hide show
  1. package/dist/alephium-web3.min.js +1 -1
  2. package/dist/alephium-web3.min.js.map +1 -1
  3. package/dist/src/api/api-alephium.d.ts +5 -0
  4. package/dist/src/api/types.d.ts +1 -1
  5. package/dist/src/codec/array-codec.d.ts +17 -0
  6. package/dist/src/codec/array-codec.js +59 -0
  7. package/dist/src/codec/asset-output-codec.d.ts +27 -0
  8. package/dist/src/codec/asset-output-codec.js +135 -0
  9. package/dist/src/codec/bigint-codec.d.ts +5 -0
  10. package/dist/src/codec/bigint-codec.js +86 -0
  11. package/dist/src/codec/bytestring-codec.d.ts +16 -0
  12. package/dist/src/codec/bytestring-codec.js +50 -0
  13. package/dist/src/codec/codec.d.ts +8 -0
  14. package/dist/src/codec/codec.js +9 -0
  15. package/dist/src/codec/compact-int-codec.d.ts +51 -0
  16. package/dist/src/codec/compact-int-codec.js +300 -0
  17. package/dist/src/codec/contract-codec.d.ts +23 -0
  18. package/dist/src/codec/contract-codec.js +81 -0
  19. package/dist/src/codec/contract-output-codec.d.ts +21 -0
  20. package/dist/src/codec/contract-output-codec.js +82 -0
  21. package/dist/src/codec/contract-output-ref-codec.d.ts +15 -0
  22. package/dist/src/codec/contract-output-ref-codec.js +38 -0
  23. package/dist/src/codec/either-codec.d.ts +17 -0
  24. package/dist/src/codec/either-codec.js +67 -0
  25. package/dist/src/codec/hash.d.ts +4 -0
  26. package/dist/src/codec/hash.js +23 -0
  27. package/dist/src/codec/index.d.ts +23 -0
  28. package/dist/src/codec/index.js +69 -0
  29. package/dist/src/codec/input-codec.d.ts +22 -0
  30. package/dist/src/codec/input-codec.js +71 -0
  31. package/dist/src/codec/instr-codec.d.ts +230 -0
  32. package/dist/src/codec/instr-codec.js +471 -0
  33. package/dist/src/codec/lockup-script-codec.d.ts +28 -0
  34. package/dist/src/codec/lockup-script-codec.js +80 -0
  35. package/dist/src/codec/long-codec.d.ts +9 -0
  36. package/dist/src/codec/long-codec.js +56 -0
  37. package/dist/src/codec/method-codec.d.ts +31 -0
  38. package/dist/src/codec/method-codec.js +78 -0
  39. package/dist/src/codec/option-codec.d.ts +15 -0
  40. package/dist/src/codec/option-codec.js +55 -0
  41. package/dist/src/codec/output-codec.d.ts +7 -0
  42. package/dist/src/codec/output-codec.js +26 -0
  43. package/dist/src/codec/script-codec.d.ts +16 -0
  44. package/dist/src/codec/script-codec.js +41 -0
  45. package/dist/src/codec/signature-codec.d.ts +14 -0
  46. package/dist/src/codec/signature-codec.js +19 -0
  47. package/dist/src/codec/signed-int-codec.d.ts +9 -0
  48. package/dist/src/codec/signed-int-codec.js +39 -0
  49. package/dist/src/codec/token-codec.d.ts +16 -0
  50. package/dist/src/codec/token-codec.js +46 -0
  51. package/dist/src/codec/transaction-codec.d.ts +27 -0
  52. package/dist/src/codec/transaction-codec.js +128 -0
  53. package/dist/src/codec/unlock-script-codec.d.ts +40 -0
  54. package/dist/src/codec/unlock-script-codec.js +170 -0
  55. package/dist/src/codec/unsigned-tx-codec.d.ts +30 -0
  56. package/dist/src/codec/unsigned-tx-codec.js +103 -0
  57. package/dist/src/contract/contract.d.ts +10 -4
  58. package/dist/src/contract/contract.js +184 -11
  59. package/dist/src/contract/ralph.d.ts +16 -0
  60. package/dist/src/contract/ralph.js +127 -1
  61. package/dist/src/index.d.ts +1 -0
  62. package/dist/src/index.js +2 -1
  63. package/dist/src/utils/address.d.ts +2 -0
  64. package/dist/src/utils/address.js +18 -6
  65. package/package.json +4 -3
  66. package/src/api/api-alephium.ts +6 -0
  67. package/src/api/types.ts +1 -1
  68. package/src/codec/array-codec.ts +63 -0
  69. package/src/codec/asset-output-codec.ts +149 -0
  70. package/src/codec/bigint-codec.ts +92 -0
  71. package/src/codec/bytestring-codec.ts +56 -0
  72. package/src/codec/codec.ts +31 -0
  73. package/src/codec/compact-int-codec.ts +316 -0
  74. package/src/codec/contract-codec.ts +95 -0
  75. package/src/codec/contract-output-codec.ts +95 -0
  76. package/src/codec/contract-output-ref-codec.ts +42 -0
  77. package/src/codec/either-codec.ts +74 -0
  78. package/src/codec/hash.ts +35 -0
  79. package/src/codec/index.ts +41 -0
  80. package/src/codec/input-codec.ts +81 -0
  81. package/src/codec/instr-codec.ts +479 -0
  82. package/src/codec/lockup-script-codec.ts +99 -0
  83. package/src/codec/long-codec.ts +59 -0
  84. package/src/codec/method-codec.ts +97 -0
  85. package/src/codec/option-codec.ts +60 -0
  86. package/src/codec/output-codec.ts +26 -0
  87. package/src/codec/script-codec.ts +45 -0
  88. package/src/codec/signature-codec.ts +40 -0
  89. package/src/codec/signed-int-codec.ts +37 -0
  90. package/src/codec/token-codec.ts +51 -0
  91. package/src/codec/transaction-codec.ts +147 -0
  92. package/src/codec/unlock-script-codec.ts +194 -0
  93. package/src/codec/unsigned-tx-codec.ts +124 -0
  94. package/src/contract/contract.ts +271 -14
  95. package/src/contract/ralph.ts +140 -2
  96. package/src/index.ts +1 -1
  97. package/src/utils/address.ts +17 -5
@@ -0,0 +1,194 @@
1
+ /*
2
+ Copyright 2018 - 2022 The Alephium Authors
3
+ This file is part of the alephium project.
4
+
5
+ The library is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Lesser General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ The library is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Lesser General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Lesser General Public License
16
+ along with the library. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+ import { Buffer } from 'buffer/'
19
+ import { Parser } from 'binary-parser'
20
+ import { ArrayCodec, DecodedArray } from './array-codec'
21
+ import { compactUnsignedIntCodec, compactSignedIntCodec, DecodedCompactInt } from './compact-int-codec'
22
+ import { Codec } from './codec'
23
+ import { Script, scriptCodec } from './script-codec'
24
+ import { ByteString, byteStringCodec } from './bytestring-codec'
25
+ import { LockupScript, lockupScriptCodec } from './lockup-script-codec'
26
+
27
+ export interface P2PKH {
28
+ publicKey: Buffer
29
+ }
30
+
31
+ class P2PKHCodec implements Codec<P2PKH> {
32
+ parser = Parser.start().buffer('publicKey', { length: 33 })
33
+
34
+ encode(input: P2PKH): Buffer {
35
+ return input.publicKey
36
+ }
37
+
38
+ decode(input: Buffer): P2PKH {
39
+ return this.parser.parse(input)
40
+ }
41
+ }
42
+ const p2pkhCodec = new P2PKHCodec()
43
+
44
+ export interface P2MPKH {
45
+ publicKeys: DecodedArray<{
46
+ publicKey: P2PKH
47
+ index: DecodedCompactInt
48
+ }>
49
+ }
50
+
51
+ class P2MPKHCodec implements Codec<P2MPKH> {
52
+ parser = Parser.start().nest('publicKeys', {
53
+ type: ArrayCodec.arrayParser(
54
+ Parser.start()
55
+ .nest('publicKey', { type: p2pkhCodec.parser })
56
+ .nest('index', { type: compactUnsignedIntCodec.parser })
57
+ )
58
+ })
59
+
60
+ encode(input: P2MPKH): Buffer {
61
+ return Buffer.concat([
62
+ Buffer.from(compactUnsignedIntCodec.encode(input.publicKeys.length)),
63
+ ...input.publicKeys.value.map((v) => {
64
+ return Buffer.concat([v.publicKey.publicKey, Buffer.from(compactUnsignedIntCodec.encode(v.index))])
65
+ })
66
+ ])
67
+ }
68
+
69
+ decode(input: Buffer): any {
70
+ return this.parser.parse(input)
71
+ }
72
+ }
73
+ const p2mpkhCodec = new P2MPKHCodec()
74
+
75
+ export interface Val {
76
+ type: number
77
+ val: number | DecodedCompactInt | ByteString | LockupScript
78
+ }
79
+ class ValCodec implements Codec<Val> {
80
+ parser = Parser.start()
81
+ .int8('type')
82
+ .choice('val', {
83
+ tag: 'type',
84
+ choices: {
85
+ 0x00: new Parser().uint8('value'), // Boolean
86
+ 0x01: compactSignedIntCodec.parser, // I256
87
+ 0x02: compactUnsignedIntCodec.parser, // U256
88
+ 0x03: byteStringCodec.parser, // ByteVec
89
+ 0x04: lockupScriptCodec.parser // Address
90
+ }
91
+ })
92
+
93
+ encode(input: Val): Buffer {
94
+ const valType = input.type
95
+
96
+ if (valType === 0x00) {
97
+ // Boolean
98
+ return Buffer.from([valType, input.val as number])
99
+ } else if (valType === 0x01) {
100
+ // I256
101
+ return Buffer.from([valType, ...compactUnsignedIntCodec.encode(input.val as DecodedCompactInt)])
102
+ } else if (valType === 0x02) {
103
+ // U256
104
+ return Buffer.from([valType, ...compactUnsignedIntCodec.encode(input.val as DecodedCompactInt)])
105
+ } else if (valType === 0x03) {
106
+ // ByteVec
107
+ return Buffer.from([valType, ...byteStringCodec.encode(input.val as ByteString)])
108
+ } else if (valType === 0x04) {
109
+ // Address
110
+ return Buffer.from([valType, ...lockupScriptCodec.encode(input.val as LockupScript)])
111
+ } else {
112
+ throw new Error(`ValCodec: unsupported val type: ${valType}`)
113
+ }
114
+ }
115
+
116
+ decode(input: Buffer): Val {
117
+ return this.parser.parse(input)
118
+ }
119
+ }
120
+
121
+ const valCodec = new ValCodec()
122
+ const valsCodec = new ArrayCodec(valCodec)
123
+
124
+ export interface P2SH {
125
+ script: Script
126
+ params: DecodedArray<Val>
127
+ }
128
+
129
+ export class P2SHCodec implements Codec<P2SH> {
130
+ parser = Parser.start()
131
+ .nest('script', {
132
+ type: scriptCodec.parser
133
+ })
134
+ .nest('params', {
135
+ type: valsCodec.parser
136
+ })
137
+
138
+ encode(input: P2SH): Buffer {
139
+ return Buffer.concat([scriptCodec.encode(input.script), valsCodec.encode(input.params.value)])
140
+ }
141
+
142
+ decode(input: Buffer): P2SH {
143
+ return this.parser.parse(input)
144
+ }
145
+ }
146
+
147
+ const p2shCodec = new P2SHCodec()
148
+
149
+ export interface UnlockScript {
150
+ scriptType: number
151
+ script: P2PKH | P2MPKH | P2SH
152
+ }
153
+
154
+ export class UnlockScriptCodec implements Codec<UnlockScript> {
155
+ parser = Parser.start()
156
+ .uint8('scriptType')
157
+ .choice('script', {
158
+ tag: 'scriptType',
159
+ choices: {
160
+ 0: p2pkhCodec.parser,
161
+ 1: p2mpkhCodec.parser,
162
+ 2: p2shCodec.parser,
163
+ 3: Parser.start() // TODO: SameAsPrevious, FIXME
164
+ }
165
+ })
166
+
167
+ encode(input: UnlockScript): Buffer {
168
+ const scriptType = input.scriptType
169
+ const inputUnLockScript = input.script
170
+ const inputUnLockScriptType = Buffer.from([scriptType])
171
+
172
+ if (scriptType === 0) {
173
+ // P2PKH
174
+ return Buffer.concat([inputUnLockScriptType, p2pkhCodec.encode(inputUnLockScript as P2PKH)])
175
+ } else if (scriptType === 1) {
176
+ // P2MPKH
177
+ return Buffer.concat([inputUnLockScriptType, p2mpkhCodec.encode(inputUnLockScript as P2MPKH)])
178
+ } else if (scriptType === 2) {
179
+ // P2SH
180
+ return Buffer.concat([inputUnLockScriptType, p2shCodec.encode(input.script as P2SH)])
181
+ } else if (scriptType === 3) {
182
+ // SameAsPrevious
183
+ return inputUnLockScriptType
184
+ } else {
185
+ throw new Error(`TODO: encode unlock script: ${scriptType}`)
186
+ }
187
+ }
188
+
189
+ decode(input: Buffer): UnlockScript {
190
+ return this.parser.parse(input)
191
+ }
192
+ }
193
+
194
+ export const unlockScriptCodec = new UnlockScriptCodec()
@@ -0,0 +1,124 @@
1
+ /*
2
+ Copyright 2018 - 2022 The Alephium Authors
3
+ This file is part of the alephium project.
4
+
5
+ The library is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Lesser General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ The library is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Lesser General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Lesser General Public License
16
+ along with the library. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+ import { Buffer } from 'buffer/'
19
+ import { Parser } from 'binary-parser'
20
+ import { UnsignedTx as ApiUnsignedTx } from '../api/api-alephium'
21
+ import { binToHex, hexToBinUnsafe } from '../utils'
22
+ import { Script, scriptCodec, statefulScriptCodecOpt } from './script-codec'
23
+ import { Option } from './option-codec'
24
+ import { DecodedCompactInt, compactSignedIntCodec, compactUnsignedIntCodec } from './compact-int-codec'
25
+ import { Input, InputCodec, inputsCodec } from './input-codec'
26
+ import { AssetOutput, AssetOutputCodec, assetOutputsCodec } from './asset-output-codec'
27
+ import { DecodedArray } from './array-codec'
28
+ import { blakeHash } from './hash'
29
+ import { Codec } from './codec'
30
+
31
+ export interface UnsignedTx {
32
+ version: number
33
+ networkId: number
34
+ statefulScript: Option<Script>
35
+ gasAmount: DecodedCompactInt
36
+ gasPrice: DecodedCompactInt
37
+ inputs: DecodedArray<Input>
38
+ fixedOutputs: DecodedArray<AssetOutput>
39
+ }
40
+
41
+ export class UnsignedTxCodec implements Codec<UnsignedTx> {
42
+ parser = new Parser()
43
+ .uint8('version')
44
+ .uint8('networkId')
45
+ .nest('statefulScript', {
46
+ type: statefulScriptCodecOpt.parser
47
+ })
48
+ .nest('gasAmount', {
49
+ type: compactSignedIntCodec.parser
50
+ })
51
+ .nest('gasPrice', {
52
+ type: compactUnsignedIntCodec.parser
53
+ })
54
+ .nest('inputs', {
55
+ type: inputsCodec.parser
56
+ })
57
+ .nest('fixedOutputs', {
58
+ type: assetOutputsCodec.parser
59
+ })
60
+
61
+ encode(decodedUnsignedTx: UnsignedTx): Buffer {
62
+ return Buffer.concat([
63
+ Buffer.from([decodedUnsignedTx.version, decodedUnsignedTx.networkId]),
64
+ statefulScriptCodecOpt.encode(decodedUnsignedTx.statefulScript),
65
+ compactSignedIntCodec.encode(decodedUnsignedTx.gasAmount),
66
+ compactUnsignedIntCodec.encode(decodedUnsignedTx.gasPrice),
67
+ inputsCodec.encode(decodedUnsignedTx.inputs.value),
68
+ assetOutputsCodec.encode(decodedUnsignedTx.fixedOutputs.value)
69
+ ])
70
+ }
71
+
72
+ decode(input: Buffer): UnsignedTx {
73
+ return this.parser.parse(input)
74
+ }
75
+
76
+ encodeApiUnsignedTx(input: ApiUnsignedTx): Buffer {
77
+ const decoded = UnsignedTxCodec.fromApiUnsignedTx(input)
78
+ return this.encode(decoded)
79
+ }
80
+
81
+ decodeApiUnsignedTx(input: Buffer): ApiUnsignedTx {
82
+ const decoded = this.parser.parse(input)
83
+ return UnsignedTxCodec.toApiUnsignedTx(decoded)
84
+ }
85
+
86
+ static txId(unsignedTx: UnsignedTx): string {
87
+ return binToHex(blakeHash(unsignedTxCodec.encode(unsignedTx)))
88
+ }
89
+
90
+ static toApiUnsignedTx(unsigned: UnsignedTx): ApiUnsignedTx {
91
+ const txId = UnsignedTxCodec.txId(unsigned)
92
+ const txIdBytes = hexToBinUnsafe(txId)
93
+ const version = unsigned.version
94
+ const networkId = unsigned.networkId
95
+ const gasAmount = compactSignedIntCodec.toI32(unsigned.gasAmount)
96
+ const gasPrice = compactUnsignedIntCodec.toU256(unsigned.gasPrice).toString()
97
+ const inputs = InputCodec.toAssetInputs(unsigned.inputs.value)
98
+ const fixedOutputs = AssetOutputCodec.toFixedAssetOutputs(txIdBytes, unsigned.fixedOutputs.value)
99
+ let scriptOpt: string | undefined = undefined
100
+ if (unsigned.statefulScript.option === 1) {
101
+ scriptOpt = scriptCodec.encode(unsigned.statefulScript.value!).toString('hex')
102
+ }
103
+
104
+ return { txId, version, networkId, gasAmount, scriptOpt, gasPrice, inputs, fixedOutputs }
105
+ }
106
+
107
+ static fromApiUnsignedTx(unsignedTx: ApiUnsignedTx): UnsignedTx {
108
+ const version = unsignedTx.version
109
+ const networkId = unsignedTx.networkId
110
+ const gasAmount = compactSignedIntCodec.fromI32(unsignedTx.gasAmount)
111
+ const gasPrice = compactUnsignedIntCodec.fromU256(BigInt(unsignedTx.gasPrice))
112
+ const inputsValue = InputCodec.fromAssetInputs(unsignedTx.inputs)
113
+ const inputs = inputsCodec.fromArray(inputsValue)
114
+ const fixedOutputsValue = AssetOutputCodec.fromFixedAssetOutputs(unsignedTx.fixedOutputs)
115
+ const fixedOutputs = assetOutputsCodec.fromArray(fixedOutputsValue)
116
+ const statefulScript = statefulScriptCodecOpt.fromBuffer(
117
+ unsignedTx.scriptOpt ? Buffer.from(unsignedTx.scriptOpt, 'hex') : undefined
118
+ )
119
+
120
+ return { version, networkId, gasAmount, gasPrice, inputs, fixedOutputs, statefulScript }
121
+ }
122
+ }
123
+
124
+ export const unsignedTxCodec = new UnsignedTxCodec()
@@ -57,7 +57,8 @@ import {
57
57
  WebCrypto,
58
58
  hexToBinUnsafe,
59
59
  isDevnet,
60
- addressFromContractId
60
+ addressFromContractId,
61
+ subContractId
61
62
  } from '../utils'
62
63
  import { getCurrentNodeProvider } from '../global'
63
64
  import * as path from 'path'
@@ -66,10 +67,24 @@ import { ONE_ALPH, TOTAL_NUMBER_OF_GROUPS } from '../constants'
66
67
  import * as blake from 'blakejs'
67
68
  import { parseError } from '../utils/error'
68
69
  import { isContractDebugMessageEnabled } from '../debug'
70
+ import {
71
+ contract,
72
+ Method,
73
+ LoadLocal,
74
+ LoadImmFieldByIndex,
75
+ LoadMutFieldByIndex,
76
+ CallerContractId,
77
+ LoadImmField,
78
+ ByteVecEq,
79
+ Assert,
80
+ StoreMutFieldByIndex,
81
+ DestroySelf
82
+ } from '../codec'
69
83
 
70
84
  const crypto = new WebCrypto()
71
85
 
72
86
  export type FieldsSig = node.FieldsSig
87
+ export type MapsSig = node.MapsSig
73
88
  export type EventSig = node.EventSig
74
89
  export type FunctionSig = node.FunctionSig
75
90
  export type Fields = NamedVals
@@ -890,6 +905,7 @@ export class Contract extends Artifact {
890
905
  readonly constants: Constant[]
891
906
  readonly enums: Enum[]
892
907
  readonly structs: Struct[]
908
+ readonly mapsSig?: MapsSig
893
909
  readonly stdInterfaceId?: HexString
894
910
 
895
911
  readonly bytecodeDebug: string
@@ -908,6 +924,7 @@ export class Contract extends Artifact {
908
924
  constants: Constant[],
909
925
  enums: Enum[],
910
926
  structs: Struct[],
927
+ mapsSig?: MapsSig,
911
928
  stdInterfaceId?: HexString
912
929
  ) {
913
930
  super(version, name, functions)
@@ -919,6 +936,7 @@ export class Contract extends Artifact {
919
936
  this.constants = constants
920
937
  this.enums = enums
921
938
  this.structs = structs
939
+ this.mapsSig = mapsSig
922
940
  this.stdInterfaceId = stdInterfaceId
923
941
 
924
942
  this.bytecodeDebug = ralph.buildDebugBytecode(this.bytecode, this.bytecodeDebugPatch)
@@ -953,6 +971,7 @@ export class Contract extends Artifact {
953
971
  artifact.constants,
954
972
  artifact.enums,
955
973
  structs,
974
+ artifact.mapsSig === null ? undefined : artifact.mapsSig,
956
975
  artifact.stdInterfaceId === null ? undefined : artifact.stdInterfaceId
957
976
  )
958
977
  return contract
@@ -972,6 +991,7 @@ export class Contract extends Artifact {
972
991
  result.constants,
973
992
  result.enums,
974
993
  structs,
994
+ result.maps,
975
995
  result.stdInterfaceId
976
996
  )
977
997
  }
@@ -1000,6 +1020,9 @@ export class Contract extends Artifact {
1000
1020
  constants: this.constants,
1001
1021
  enums: this.enums
1002
1022
  }
1023
+ if (this.mapsSig !== undefined) {
1024
+ object.mapsSig = this.mapsSig
1025
+ }
1003
1026
  if (this.stdInterfaceId !== undefined) {
1004
1027
  object.stdInterfaceId = this.stdInterfaceId
1005
1028
  }
@@ -1542,7 +1565,16 @@ function toApiInputAssets(inputAssets?: InputAsset[]): node.TestInputAsset[] | u
1542
1565
  return typeof inputAssets !== 'undefined' ? inputAssets.map(toApiInputAsset) : undefined
1543
1566
  }
1544
1567
 
1545
- export interface TestContractParams<F extends Fields = Fields, A extends Arguments = Arguments> {
1568
+ export type TestContractParamsWithoutMaps<F extends Fields = Fields, A extends Arguments = Arguments> = Omit<
1569
+ TestContractParams<F, A>,
1570
+ 'initialMaps'
1571
+ >
1572
+
1573
+ export interface TestContractParams<
1574
+ F extends Fields = Fields,
1575
+ A extends Arguments = Arguments,
1576
+ M extends Record<string, Map<Val, Val>> = Record<string, Map<Val, Val>>
1577
+ > {
1546
1578
  group?: number // default 0
1547
1579
  address?: string
1548
1580
  callerAddress?: string
@@ -1550,6 +1582,7 @@ export interface TestContractParams<F extends Fields = Fields, A extends Argumen
1550
1582
  blockTimeStamp?: number
1551
1583
  txId?: string
1552
1584
  initialFields: F
1585
+ initialMaps?: M
1553
1586
  initialAsset?: Asset // default 1 ALPH
1554
1587
  testArgs: A
1555
1588
  existingContracts?: ContractState[] // default no existing contracts
@@ -1567,11 +1600,14 @@ export interface ContractEvent<T extends Fields = Fields> {
1567
1600
 
1568
1601
  export type DebugMessage = node.DebugMessage
1569
1602
 
1570
- export interface TestContractResult<R> {
1603
+ export type TestContractResultWithoutMaps<R> = Omit<TestContractResult<R>, 'maps'>
1604
+
1605
+ export interface TestContractResult<R, M extends Record<string, Map<Val, Val>> = Record<string, Map<Val, Val>>> {
1571
1606
  contractId: string
1572
1607
  contractAddress: string
1573
1608
  returns: R
1574
1609
  gasUsed: number
1610
+ maps?: M
1575
1611
  contracts: ContractState[]
1576
1612
  txOutputs: Output[]
1577
1613
  events: ContractEvent[]
@@ -1824,23 +1860,244 @@ export function addStdIdToFields<F extends Fields>(
1824
1860
  : { ...fields, __stdInterfaceId: stdInterfaceIdPrefix + contract.stdInterfaceId }
1825
1861
  }
1826
1862
 
1827
- export async function testMethod<I extends ContractInstance, F extends Fields, A extends Arguments, R>(
1828
- contract: ContractFactory<I, F>,
1863
+ function calcWrapperContractId(
1864
+ parentContractId: string,
1865
+ mapIndex: number,
1866
+ key: Val,
1867
+ keyType: string,
1868
+ group: number
1869
+ ): string {
1870
+ const prefix = ralph.encodeMapPrefix(mapIndex)
1871
+ const encodedKey = ralph.primitiveToByteVec(key, keyType)
1872
+ const path = binToHex(prefix) + binToHex(encodedKey)
1873
+ return subContractId(parentContractId, path, group)
1874
+ }
1875
+
1876
+ function genCodeForType(type: string, structs: Struct[]): { bytecode: string; codeHash: string } {
1877
+ const { immFields, mutFields } = ralph.calcFieldSize(type, true, structs)
1878
+ const loadImmFieldByIndex: Method = {
1879
+ isPublic: true,
1880
+ assetModifier: 0,
1881
+ argsLength: 1,
1882
+ localsLength: 1,
1883
+ returnLength: 1,
1884
+ instrs: [LoadLocal(0), LoadImmFieldByIndex]
1885
+ }
1886
+ const loadMutFieldByIndex: Method = {
1887
+ ...loadImmFieldByIndex,
1888
+ instrs: [LoadLocal(0), LoadMutFieldByIndex]
1889
+ }
1890
+ const parentContractIdIndex = immFields
1891
+ const storeMutFieldByIndex: Method = {
1892
+ ...loadImmFieldByIndex,
1893
+ argsLength: 2,
1894
+ localsLength: 2,
1895
+ returnLength: 0,
1896
+ instrs: [
1897
+ CallerContractId,
1898
+ LoadImmField(parentContractIdIndex),
1899
+ ByteVecEq,
1900
+ Assert,
1901
+ LoadLocal(0), // value
1902
+ LoadLocal(1), // index
1903
+ StoreMutFieldByIndex
1904
+ ]
1905
+ }
1906
+ const destroy: Method = {
1907
+ isPublic: true,
1908
+ assetModifier: 2,
1909
+ argsLength: 1,
1910
+ localsLength: 1,
1911
+ returnLength: 0,
1912
+ instrs: [CallerContractId, LoadImmField(parentContractIdIndex), ByteVecEq, Assert, LoadLocal(0), DestroySelf]
1913
+ }
1914
+ const c = {
1915
+ fieldLength: immFields + mutFields + 1, // parentContractId
1916
+ methods: [loadImmFieldByIndex, loadMutFieldByIndex, storeMutFieldByIndex, destroy]
1917
+ }
1918
+ const bytecode = contract.contractCodec.encode(contract.toHalfDecoded(c))
1919
+ const codeHash = blake.blake2b(bytecode, undefined, 32)
1920
+ return { bytecode: binToHex(bytecode), codeHash: binToHex(codeHash) }
1921
+ }
1922
+
1923
+ function getContractFieldsSig(mapValueType: string): FieldsSig {
1924
+ return {
1925
+ names: ['value', 'parentContractId'],
1926
+ types: [mapValueType, 'ByteVec'],
1927
+ isMutable: [true, false]
1928
+ }
1929
+ }
1930
+
1931
+ function mapToExistingContracts(
1932
+ contract: Contract,
1933
+ parentContractId: string,
1934
+ group: number,
1935
+ map: Map<Val, Val>,
1936
+ mapIndex: number,
1937
+ type: string
1938
+ ): ContractState[] {
1939
+ const [keyType, valueType] = ralph.parseMapType(type)
1940
+ const generatedContract = genCodeForType(valueType, contract.structs)
1941
+ return Array.from(map.entries()).map(([key, value]) => {
1942
+ const fields = { value, parentContractId }
1943
+ const contractId = calcWrapperContractId(parentContractId, mapIndex, key, keyType, group)
1944
+ return {
1945
+ ...generatedContract,
1946
+ address: addressFromContractId(contractId),
1947
+ contractId: contractId,
1948
+ fieldsSig: getContractFieldsSig(valueType),
1949
+ fields,
1950
+ asset: { alphAmount: ONE_ALPH }
1951
+ }
1952
+ })
1953
+ }
1954
+
1955
+ function mapsToExistingContracts(
1956
+ contract: Contract,
1957
+ parentContractId: string,
1958
+ group: number,
1959
+ initialMaps: Record<string, Map<Val, Val>>
1960
+ ) {
1961
+ const mapsSig = contract.mapsSig
1962
+ if (mapsSig === undefined) return []
1963
+ const contractStates: ContractState[] = []
1964
+ Object.keys(initialMaps).forEach((name) => {
1965
+ const index = mapsSig.names.findIndex((n) => n === name)
1966
+ if (index === -1) throw new Error(`Map var ${name} does not exist in contract ${contract.name}`)
1967
+ const mapType = mapsSig.types[`${index}`]
1968
+ const states = mapToExistingContracts(contract, parentContractId, group, initialMaps[`${name}`], index, mapType)
1969
+ contractStates.push(...states)
1970
+ })
1971
+ return contractStates
1972
+ }
1973
+
1974
+ export async function testMethod<
1975
+ I extends ContractInstance,
1976
+ F extends Fields,
1977
+ A extends Arguments,
1978
+ R,
1979
+ M extends Record<string, Map<Val, Val>> = Record<string, Map<Val, Val>>
1980
+ >(
1981
+ factory: ContractFactory<I, F>,
1829
1982
  methodName: string,
1830
- params: Optional<TestContractParams<F, A>, 'testArgs' | 'initialFields'>
1831
- ): Promise<TestContractResult<R>> {
1983
+ params: Optional<TestContractParams<F, A, M>, 'testArgs' | 'initialFields'>
1984
+ ): Promise<TestContractResult<R, M>> {
1832
1985
  const txId = params?.txId ?? randomTxId()
1833
- const initialFields = params.initialFields === undefined ? {} : params.initialFields
1834
- const apiParams = contract.contract.toApiTestContractParams(methodName, {
1986
+ const contract = factory.contract
1987
+ const address = params.address ?? addressFromContractId(binToHex(crypto.getRandomValues(new Uint8Array(32))))
1988
+ const contractId = binToHex(contractIdFromAddress(address))
1989
+ const group = params.group ?? 0
1990
+ const initialMaps = params.initialMaps ?? {}
1991
+ const contractStates = mapsToExistingContracts(contract, contractId, group, initialMaps)
1992
+ const apiParams = contract.toApiTestContractParams(methodName, {
1835
1993
  ...params,
1994
+ address,
1836
1995
  txId: txId,
1837
- initialFields: addStdIdToFields(contract.contract, initialFields),
1838
- testArgs: params.testArgs === undefined ? {} : params.testArgs
1996
+ initialFields: addStdIdToFields(contract, params.initialFields ?? {}),
1997
+ testArgs: params.testArgs === undefined ? {} : params.testArgs,
1998
+ existingContracts: (params.existingContracts ?? []).concat(contractStates)
1839
1999
  })
1840
2000
  const apiResult = await getCurrentNodeProvider().contracts.postContractsTestContract(apiParams)
1841
- const testResult = contract.contract.fromApiTestContractResult(methodName, apiResult, txId)
1842
- contract.contract.printDebugMessages(methodName, testResult.debugMessages)
1843
- return testResult as TestContractResult<R>
2001
+ const maps = existingContractsToMaps(contract, address, group, apiResult, initialMaps)
2002
+ const testResult = contract.fromApiTestContractResult(methodName, apiResult, txId)
2003
+ contract.printDebugMessages(methodName, testResult.debugMessages)
2004
+ return {
2005
+ ...testResult,
2006
+ maps
2007
+ } as TestContractResult<R, M>
2008
+ }
2009
+
2010
+ interface MapInfo {
2011
+ name: string
2012
+ value: Map<Val, Val>
2013
+ keyType: string
2014
+ valueType: string
2015
+ index: number
2016
+ }
2017
+
2018
+ function buildMapInfo(contract: Contract, fields: Fields): MapInfo[] {
2019
+ const mapsSig = contract.mapsSig
2020
+ if (mapsSig === undefined) return []
2021
+ return mapsSig.names.map((name, index) => {
2022
+ const mapType = mapsSig.types[`${index}`]
2023
+ const value = (fields[`${name}`] ?? new Map<Val, Val>()) as Map<Val, Val>
2024
+ const [keyType, valueType] = ralph.parseMapType(mapType)
2025
+ return { name, value, keyType, valueType, index }
2026
+ })
2027
+ }
2028
+
2029
+ function extractFromEventLog(
2030
+ contract: Contract,
2031
+ result: node.TestContractResult,
2032
+ allMaps: MapInfo[],
2033
+ address: string,
2034
+ group: number
2035
+ ): string[] {
2036
+ const parentContractId = binToHex(contractIdFromAddress(address))
2037
+ const newInserted: string[] = []
2038
+ result.debugMessages.forEach((message) => {
2039
+ if (message.contractAddress !== address) return
2040
+ const decoded = ralph.tryDecodeMapDebugLog(message.message)
2041
+ if (decoded === undefined) return
2042
+ const map = allMaps[`${decoded.mapIndex}`]
2043
+ const decodedKey = ralph.decodePrimitive(decoded.encodedKey, map.keyType)
2044
+ const contractId = subContractId(parentContractId, decoded.path, group)
2045
+ if (!decoded.isInsert) {
2046
+ map.value.delete(decodedKey)
2047
+ return
2048
+ }
2049
+ const state = result.contracts.find((s) => s.address === addressFromContractId(contractId))
2050
+ if (state === undefined) {
2051
+ throw new Error(`Cannot find contract state for map value, map field: ${map.name}, value type: ${map.valueType}`)
2052
+ }
2053
+ newInserted.push(state.address)
2054
+ const fieldsSig = getContractFieldsSig(map.valueType)
2055
+ const fields = fromApiFields(state.immFields, state.mutFields, fieldsSig, contract.structs)
2056
+ map.value.set(decodedKey, fields['value'])
2057
+ })
2058
+ return newInserted
2059
+ }
2060
+
2061
+ function updateMaps(
2062
+ contract: Contract,
2063
+ result: node.TestContractResult,
2064
+ allMaps: MapInfo[],
2065
+ address: Address,
2066
+ group: number
2067
+ ): string[] {
2068
+ const parentContractId = binToHex(contractIdFromAddress(address))
2069
+ const updated: string[] = []
2070
+ allMaps.forEach((map) => {
2071
+ Array.from(map.value.keys()).forEach((key) => {
2072
+ const contractId = calcWrapperContractId(parentContractId, map.index, key, map.keyType, group)
2073
+ const updatedState = result.contracts.find((s) => s.address === addressFromContractId(contractId))
2074
+ if (updatedState === undefined) return
2075
+ updated.push(updatedState.address)
2076
+ const fieldsSig = getContractFieldsSig(map.valueType)
2077
+ const fields = fromApiFields(updatedState.immFields, updatedState.mutFields, fieldsSig, contract.structs)
2078
+ map.value.set(key, fields['value'])
2079
+ })
2080
+ })
2081
+ return updated
2082
+ }
2083
+
2084
+ function existingContractsToMaps(
2085
+ contract: Contract,
2086
+ address: Address,
2087
+ group: number,
2088
+ result: node.TestContractResult,
2089
+ maps: Record<string, Map<Val, Val>>
2090
+ ): Record<string, Map<Val, Val>> {
2091
+ const allMaps = buildMapInfo(contract, maps)
2092
+ const updated = updateMaps(contract, result, allMaps, address, group)
2093
+ const newInserted = extractFromEventLog(contract, result, allMaps, address, group)
2094
+ const mapEntries = updated.concat(newInserted)
2095
+ const remainContracts = result.contracts.filter((c) => mapEntries.find((addr) => c.address === addr) === undefined)
2096
+ result.contracts = remainContracts
2097
+ return allMaps.reduce((acc, map) => {
2098
+ acc[`${map.name}`] = map.value
2099
+ return acc
2100
+ }, {})
1844
2101
  }
1845
2102
 
1846
2103
  export abstract class ContractInstance {