@alephium/web3 1.3.0 → 1.5.0

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 (107) 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/address/address.js +11 -5
  4. package/dist/src/api/api-alephium.d.ts +2 -4
  5. package/dist/src/api/api-alephium.js +1 -1
  6. package/dist/src/codec/array-codec.d.ts +4 -12
  7. package/dist/src/codec/array-codec.js +15 -28
  8. package/dist/src/codec/asset-output-codec.d.ts +6 -11
  9. package/dist/src/codec/asset-output-codec.js +32 -71
  10. package/dist/src/codec/bigint-codec.d.ts +2 -1
  11. package/dist/src/codec/bigint-codec.js +14 -2
  12. package/dist/src/codec/bytestring-codec.d.ts +6 -11
  13. package/dist/src/codec/bytestring-codec.js +9 -23
  14. package/dist/src/codec/codec.d.ts +54 -5
  15. package/dist/src/codec/codec.js +112 -14
  16. package/dist/src/codec/compact-int-codec.d.ts +65 -44
  17. package/dist/src/codec/compact-int-codec.js +222 -204
  18. package/dist/src/codec/contract-codec.d.ts +5 -8
  19. package/dist/src/codec/contract-codec.js +15 -29
  20. package/dist/src/codec/contract-output-codec.d.ts +4 -10
  21. package/dist/src/codec/contract-output-codec.js +20 -40
  22. package/dist/src/codec/contract-output-ref-codec.d.ts +2 -8
  23. package/dist/src/codec/contract-output-ref-codec.js +7 -17
  24. package/dist/src/codec/either-codec.d.ts +8 -15
  25. package/dist/src/codec/either-codec.js +5 -46
  26. package/dist/src/codec/index.d.ts +4 -3
  27. package/dist/src/codec/index.js +7 -4
  28. package/dist/src/codec/input-codec.d.ts +4 -10
  29. package/dist/src/codec/input-codec.js +11 -46
  30. package/dist/src/codec/instr-codec.d.ts +633 -40
  31. package/dist/src/codec/instr-codec.js +1040 -434
  32. package/dist/src/codec/int-as-4bytes-codec.d.ts +7 -0
  33. package/dist/src/codec/{signed-int-codec.js → int-as-4bytes-codec.js} +6 -12
  34. package/dist/src/codec/lockup-script-codec.d.ts +23 -26
  35. package/dist/src/codec/lockup-script-codec.js +12 -58
  36. package/dist/src/codec/method-codec.d.ts +6 -18
  37. package/dist/src/codec/method-codec.js +20 -48
  38. package/dist/src/codec/option-codec.d.ts +8 -13
  39. package/dist/src/codec/option-codec.js +14 -32
  40. package/dist/src/codec/output-codec.d.ts +2 -2
  41. package/dist/src/codec/output-codec.js +1 -1
  42. package/dist/src/codec/reader.d.ts +8 -0
  43. package/dist/src/codec/reader.js +48 -0
  44. package/dist/src/codec/script-codec.d.ts +6 -14
  45. package/dist/src/codec/script-codec.js +6 -22
  46. package/dist/src/codec/signature-codec.d.ts +4 -12
  47. package/dist/src/codec/signature-codec.js +3 -15
  48. package/dist/src/codec/timestamp-codec.d.ts +8 -0
  49. package/dist/src/codec/timestamp-codec.js +39 -0
  50. package/dist/src/codec/token-codec.d.ts +3 -10
  51. package/dist/src/codec/token-codec.js +6 -24
  52. package/dist/src/codec/transaction-codec.d.ts +6 -11
  53. package/dist/src/codec/transaction-codec.js +24 -49
  54. package/dist/src/codec/unlock-script-codec.d.ts +25 -36
  55. package/dist/src/codec/unlock-script-codec.js +26 -147
  56. package/dist/src/codec/unsigned-tx-codec.d.ts +8 -14
  57. package/dist/src/codec/unsigned-tx-codec.js +24 -66
  58. package/dist/src/codec/val.d.ts +27 -0
  59. package/dist/src/codec/val.js +33 -0
  60. package/dist/src/contract/contract.d.ts +1 -0
  61. package/dist/src/contract/contract.js +20 -13
  62. package/dist/src/contract/index.d.ts +1 -0
  63. package/dist/src/contract/index.js +1 -0
  64. package/dist/src/contract/ralph.d.ts +0 -4
  65. package/dist/src/contract/ralph.js +50 -179
  66. package/dist/src/contract/script-simulator.d.ts +27 -0
  67. package/dist/src/contract/script-simulator.js +757 -0
  68. package/dist/src/exchange/exchange.js +1 -1
  69. package/package.json +3 -4
  70. package/src/address/address.ts +15 -9
  71. package/src/api/api-alephium.ts +2 -4
  72. package/src/codec/array-codec.ts +16 -34
  73. package/src/codec/asset-output-codec.ts +38 -83
  74. package/src/codec/bigint-codec.ts +16 -2
  75. package/src/codec/bytestring-codec.ts +10 -28
  76. package/src/codec/codec.ts +121 -15
  77. package/src/codec/compact-int-codec.ts +230 -207
  78. package/src/codec/contract-codec.ts +20 -33
  79. package/src/codec/contract-output-codec.ts +22 -48
  80. package/src/codec/contract-output-ref-codec.ts +6 -17
  81. package/src/codec/either-codec.ts +4 -53
  82. package/src/codec/index.ts +4 -3
  83. package/src/codec/input-codec.ts +14 -36
  84. package/src/codec/instr-codec.ts +1229 -455
  85. package/src/codec/{signed-int-codec.ts → int-as-4bytes-codec.ts} +6 -10
  86. package/src/codec/lockup-script-codec.ts +28 -76
  87. package/src/codec/method-codec.ts +23 -61
  88. package/src/codec/option-codec.ts +13 -36
  89. package/src/codec/output-codec.ts +2 -2
  90. package/src/codec/reader.ts +56 -0
  91. package/src/codec/script-codec.ts +9 -31
  92. package/src/codec/signature-codec.ts +3 -18
  93. package/src/codec/timestamp-codec.ts +42 -0
  94. package/src/codec/token-codec.ts +7 -26
  95. package/src/codec/transaction-codec.ts +29 -58
  96. package/src/codec/unlock-script-codec.ts +44 -171
  97. package/src/codec/unsigned-tx-codec.ts +34 -63
  98. package/src/codec/val.ts +40 -0
  99. package/src/contract/contract.ts +24 -20
  100. package/src/contract/index.ts +1 -0
  101. package/src/contract/ralph.ts +76 -172
  102. package/src/contract/script-simulator.ts +838 -0
  103. package/src/exchange/exchange.ts +1 -1
  104. package/dist/src/codec/long-codec.d.ts +0 -8
  105. package/dist/src/codec/long-codec.js +0 -55
  106. package/dist/src/codec/signed-int-codec.d.ts +0 -8
  107. package/src/codec/long-codec.ts +0 -58
@@ -0,0 +1,838 @@
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
+
19
+ import { addressFromContractId } from '../address'
20
+ import { Token } from '../api'
21
+ import { boolCodec, Method, unsignedTxCodec } from '../codec'
22
+ import { LockupScript, lockupScriptCodec } from '../codec/lockup-script-codec'
23
+ import { Script } from '../codec/script-codec'
24
+ import { Val, ValAddress, ValBool, ValByteVec, ValI256, ValU256 } from '../codec/val'
25
+ import { ALPH_TOKEN_ID } from '../constants'
26
+ import { binToHex, HexString, hexToBinUnsafe } from '../utils'
27
+
28
+ /**
29
+ * Contract call extracted from a script
30
+ * @param contractAddress the address of the contract
31
+ * @param approvedAttoAlphAmount the amount of ALPH approved to the contract
32
+ * - undefined if no ALPH is approved
33
+ * - 'unknown' if the amount cannot be determined
34
+ * - a number if the amount is known
35
+ * @param approvedTokens the tokens approved to the contract
36
+ * - undefined if no tokens are approved
37
+ * - 'unknown' if the tokens cannot be determined
38
+ * - an array of tokens if the tokens are known
39
+ */
40
+ export interface ContractCall {
41
+ contractAddress: string
42
+ approvedAttoAlphAmount?: bigint | 'unknown'
43
+ approvedTokens?: Token[] | 'unknown'
44
+ }
45
+
46
+ export class ScriptSimulator {
47
+ // This function without errors is recommended for now as the simulator does not support all instructions
48
+ static extractContractCalls(unsignedTx: HexString): ContractCall[] {
49
+ try {
50
+ return this.extractContractCallsWithErrors(unsignedTx)
51
+ } catch (e) {
52
+ console.debug('Error extracting contract calls from script', e)
53
+ return []
54
+ }
55
+ }
56
+
57
+ static extractContractCallsWithErrors(unsignedTx: HexString): ContractCall[] {
58
+ const unsignedTxBytes = hexToBinUnsafe(unsignedTx)
59
+ const decodedUnsignedTx = unsignedTxCodec.decode(unsignedTxBytes)
60
+ const scriptOpt = decodedUnsignedTx.statefulScript
61
+ switch (scriptOpt.kind) {
62
+ case 'Some': {
63
+ return this.extractContractCallsFromScript(scriptOpt.value)
64
+ }
65
+ case 'None': {
66
+ return []
67
+ }
68
+ }
69
+ }
70
+
71
+ static extractContractCallsFromScript(script: Script): ContractCall[] {
72
+ const methods = script.methods
73
+ if (methods.length === 0) {
74
+ return []
75
+ }
76
+
77
+ const mainMethod = methods[0]
78
+ return this.extractContractCallsFromMainMethod(mainMethod)
79
+ }
80
+
81
+ static extractContractCallsFromMainMethod(mainMethod: Method): ContractCall[] {
82
+ const operandStack = new Stack()
83
+ const localVariables = new LocalVariables()
84
+ const contractCalls: ContractCall[] = []
85
+ const callerAddress: ValAddress = {
86
+ kind: 'Address',
87
+ value: { kind: 'P2PKH', value: random32Bytes() }
88
+ }
89
+ const approved = new ApprovedAccumulator()
90
+ for (const instr of mainMethod.instrs) {
91
+ switch (instr.name) {
92
+ case 'ConstTrue':
93
+ operandStack.push({ kind: 'Bool', value: true })
94
+ break
95
+ case 'ConstFalse':
96
+ operandStack.push({ kind: 'Bool', value: false })
97
+ break
98
+ case 'I256Const0':
99
+ operandStack.push({ kind: 'I256', value: 0n })
100
+ break
101
+ case 'I256Const1':
102
+ operandStack.push({ kind: 'I256', value: 1n })
103
+ break
104
+ case 'I256Const2':
105
+ operandStack.push({ kind: 'I256', value: 2n })
106
+ break
107
+ case 'I256Const3':
108
+ operandStack.push({ kind: 'I256', value: 3n })
109
+ break
110
+ case 'I256Const4':
111
+ operandStack.push({ kind: 'I256', value: 4n })
112
+ break
113
+ case 'I256Const5':
114
+ operandStack.push({ kind: 'I256', value: 5n })
115
+ break
116
+ case 'I256ConstN1':
117
+ operandStack.push({ kind: 'I256', value: -1n })
118
+ break
119
+ case 'I256Const':
120
+ operandStack.push({ kind: 'I256', value: instr.value })
121
+ break
122
+ case 'U256Const0':
123
+ operandStack.push({ kind: 'U256', value: 0n })
124
+ break
125
+ case 'U256Const1':
126
+ operandStack.push({ kind: 'U256', value: 1n })
127
+ break
128
+ case 'U256Const2':
129
+ operandStack.push({ kind: 'U256', value: 2n })
130
+ break
131
+ case 'U256Const3':
132
+ operandStack.push({ kind: 'U256', value: 3n })
133
+ break
134
+ case 'U256Const4':
135
+ operandStack.push({ kind: 'U256', value: 4n })
136
+ break
137
+ case 'U256Const5':
138
+ operandStack.push({ kind: 'U256', value: 5n })
139
+ break
140
+ case 'U256Const':
141
+ operandStack.push({ kind: 'U256', value: instr.value })
142
+ break
143
+ case 'BytesConst':
144
+ operandStack.push({ kind: 'ByteVec', value: instr.value })
145
+ break
146
+ case 'AddressConst':
147
+ operandStack.push({ kind: 'Address', value: instr.value })
148
+ break
149
+ case 'LoadLocal':
150
+ operandStack.push(localVariables.get(instr.index))
151
+ break
152
+ case 'StoreLocal':
153
+ localVariables.set(instr.index, operandStack.pop())
154
+ break
155
+ case 'Pop':
156
+ operandStack.pop()
157
+ break
158
+ case 'Dup':
159
+ const val = operandStack.pop()
160
+ operandStack.push(val)
161
+ operandStack.push(val)
162
+ break
163
+ case 'Swap':
164
+ const val1 = operandStack.pop()
165
+ const val2 = operandStack.pop()
166
+ operandStack.push(val1)
167
+ operandStack.push(val2)
168
+ break
169
+ case 'BoolNot':
170
+ const bool = operandStack.popBool()
171
+ const result = unaryOp<'Bool', boolean>(bool, (x) => !x)
172
+ operandStack.push(result)
173
+ case 'BoolAnd': {
174
+ const bool1 = operandStack.popBool()
175
+ const bool2 = operandStack.popBool()
176
+ binaryOp<'Bool', boolean>(bool1, bool2, (x, y) => x && y, operandStack.push)
177
+ break
178
+ }
179
+ case 'BoolOr': {
180
+ const bool1 = operandStack.popBool()
181
+ const bool2 = operandStack.popBool()
182
+ binaryOp<'Bool', boolean>(bool1, bool2, (x, y) => x || y, operandStack.push)
183
+ break
184
+ }
185
+ case 'BoolEq': {
186
+ const bool1 = operandStack.popBool()
187
+ const bool2 = operandStack.popBool()
188
+ binaryOp<'Bool', boolean>(bool1, bool2, (x, y) => x === y, operandStack.push)
189
+ break
190
+ }
191
+ case 'BoolNeq': {
192
+ const bool1 = operandStack.popBool()
193
+ const bool2 = operandStack.popBool()
194
+ binaryOp<'Bool', boolean>(bool1, bool2, (x, y) => x !== y, operandStack.push)
195
+ break
196
+ }
197
+ case 'BoolToByteVec': {
198
+ const bool = operandStack.popBool()
199
+ if (bool.kind === 'Symbol-Bool') {
200
+ operandStack.push(bool)
201
+ } else {
202
+ operandStack.push({ kind: 'ByteVec', value: boolCodec.encode(bool.value) })
203
+ }
204
+ break
205
+ }
206
+ case 'I256Add': {
207
+ // unsafe
208
+ const i256_2 = operandStack.popI256()
209
+ const i256_1 = operandStack.popI256()
210
+ binaryOp<'I256', bigint>(i256_1, i256_2, (x, y) => x + y, operandStack.push)
211
+ break
212
+ }
213
+ case 'I256Sub': {
214
+ // unsafe
215
+ const i256_2 = operandStack.popI256()
216
+ const i256_1 = operandStack.popI256()
217
+ binaryOp<'I256', bigint>(i256_1, i256_2, (x, y) => x - y, operandStack.push)
218
+ break
219
+ }
220
+ case 'I256Mul': {
221
+ // unsafe
222
+ const i256_2 = operandStack.popI256()
223
+ const i256_1 = operandStack.popI256()
224
+ binaryOp<'I256', bigint>(i256_1, i256_2, (x, y) => x * y, operandStack.push)
225
+ break
226
+ }
227
+ case 'I256Div': {
228
+ // unsafe
229
+ const i256_2 = operandStack.popI256()
230
+ const i256_1 = operandStack.popI256()
231
+ binaryOp<'I256', bigint>(i256_1, i256_2, (x, y) => x / y, operandStack.push)
232
+ break
233
+ }
234
+ case 'I256Eq': {
235
+ // unsafe
236
+ const i256_2 = operandStack.popI256()
237
+ const i256_1 = operandStack.popI256()
238
+ comparisonOp<'I256', bigint>(i256_1, i256_2, (x, y) => x === y, operandStack.push)
239
+ break
240
+ }
241
+ case 'I256Neq': {
242
+ // unsafe
243
+ const i256_2 = operandStack.popI256()
244
+ const i256_1 = operandStack.popI256()
245
+ comparisonOp<'I256', bigint>(i256_1, i256_2, (x, y) => x !== y, operandStack.push)
246
+ break
247
+ }
248
+ case 'I256Lt': {
249
+ // unsafe
250
+ const i256_2 = operandStack.popI256()
251
+ const i256_1 = operandStack.popI256()
252
+ comparisonOp<'I256', bigint>(i256_1, i256_2, (x, y) => x < y, operandStack.push)
253
+ break
254
+ }
255
+ case 'I256Le': {
256
+ // unsafe
257
+ const i256_2 = operandStack.popI256()
258
+ const i256_1 = operandStack.popI256()
259
+ comparisonOp<'I256', bigint>(i256_1, i256_2, (x, y) => x <= y, operandStack.push)
260
+ break
261
+ }
262
+ case 'I256Gt': {
263
+ // unsafe
264
+ const i256_2 = operandStack.popI256()
265
+ const i256_1 = operandStack.popI256()
266
+ comparisonOp<'I256', bigint>(i256_1, i256_2, (x, y) => x > y, operandStack.push)
267
+ break
268
+ }
269
+ case 'I256Ge': {
270
+ // unsafe
271
+ const i256_2 = operandStack.popI256()
272
+ const i256_1 = operandStack.popI256()
273
+ comparisonOp<'I256', bigint>(i256_1, i256_2, (x, y) => x >= y, operandStack.push)
274
+ break
275
+ }
276
+ case 'U256Add': {
277
+ // unsafe
278
+ const u256_2 = operandStack.popU256()
279
+ const u256_1 = operandStack.popU256()
280
+ binaryOp<'U256', bigint>(u256_1, u256_2, (x, y) => x + y, operandStack.push)
281
+ break
282
+ }
283
+ case 'U256Sub': {
284
+ // unsafe
285
+ const u256_2 = operandStack.popU256()
286
+ const u256_1 = operandStack.popU256()
287
+ binaryOp<'U256', bigint>(u256_1, u256_2, (x, y) => x - y, operandStack.push)
288
+ break
289
+ }
290
+ case 'U256Mul': {
291
+ // unsafe
292
+ const u256_2 = operandStack.popU256()
293
+ const u256_1 = operandStack.popU256()
294
+ binaryOp<'U256', bigint>(u256_1, u256_2, (x, y) => x * y, operandStack.push)
295
+ break
296
+ }
297
+ case 'U256Div': {
298
+ // unsafe
299
+ const u256_2 = operandStack.popU256()
300
+ const u256_1 = operandStack.popU256()
301
+ binaryOp<'U256', bigint>(u256_1, u256_2, (x, y) => x / y, operandStack.push)
302
+ break
303
+ }
304
+ case 'U256Eq': {
305
+ // unsafe
306
+ const u256_2 = operandStack.popU256()
307
+ const u256_1 = operandStack.popU256()
308
+ comparisonOp<'U256', bigint>(u256_1, u256_2, (x, y) => x === y, operandStack.push)
309
+ break
310
+ }
311
+ case 'U256Neq': {
312
+ // unsafe
313
+ const u256_2 = operandStack.popU256()
314
+ const u256_1 = operandStack.popU256()
315
+ comparisonOp<'U256', bigint>(u256_1, u256_2, (x, y) => x !== y, operandStack.push)
316
+ break
317
+ }
318
+ case 'U256Lt': {
319
+ // unsafe
320
+ const u256_2 = operandStack.popU256()
321
+ const u256_1 = operandStack.popU256()
322
+ comparisonOp<'U256', bigint>(u256_1, u256_2, (x, y) => x < y, operandStack.push)
323
+ break
324
+ }
325
+ case 'U256Le': {
326
+ // unsafe
327
+ const u256_2 = operandStack.popU256()
328
+ const u256_1 = operandStack.popU256()
329
+ comparisonOp<'U256', bigint>(u256_1, u256_2, (x, y) => x <= y, operandStack.push)
330
+ break
331
+ }
332
+ case 'U256Gt': {
333
+ // unsafe
334
+ const u256_2 = operandStack.popU256()
335
+ const u256_1 = operandStack.popU256()
336
+ comparisonOp<'U256', bigint>(u256_1, u256_2, (x, y) => x > y, operandStack.push)
337
+ break
338
+ }
339
+ case 'U256Ge': {
340
+ // unsafe
341
+ const u256_2 = operandStack.popU256()
342
+ const u256_1 = operandStack.popU256()
343
+ comparisonOp<'U256', bigint>(u256_1, u256_2, (x, y) => x >= y, operandStack.push)
344
+ break
345
+ }
346
+ case 'ByteVecEq': {
347
+ const byteVec1 = operandStack.popByteVec()
348
+ const byteVec2 = operandStack.popByteVec()
349
+ comparisonOp<'ByteVec', Uint8Array>(byteVec1, byteVec2, (x, y) => arrayEquals(x, y), operandStack.push)
350
+ break
351
+ }
352
+ case 'ByteVecNeq': {
353
+ const byteVec1 = operandStack.popByteVec()
354
+ const byteVec2 = operandStack.popByteVec()
355
+ comparisonOp<'ByteVec', Uint8Array>(byteVec1, byteVec2, (x, y) => !arrayEquals(x, y), operandStack.push)
356
+ break
357
+ }
358
+ case 'ByteVecSize': {
359
+ const byteVec = operandStack.popByteVec()
360
+ if (byteVec.kind === 'Symbol-ByteVec') {
361
+ operandStack.push({ kind: 'Symbol-U256', value: undefined })
362
+ } else {
363
+ operandStack.push({ kind: 'U256', value: BigInt(byteVec.value.length) })
364
+ }
365
+ break
366
+ }
367
+ case 'ByteVecConcat': {
368
+ const byteVec2 = operandStack.popByteVec()
369
+ const byteVec1 = operandStack.popByteVec()
370
+ binaryOp<'ByteVec', Uint8Array>(byteVec1, byteVec2, (x, y) => new Uint8Array([...x, ...y]), operandStack.push)
371
+ break
372
+ }
373
+ case 'ByteVecSlice': {
374
+ const end = operandStack.popU256()
375
+ const start = operandStack.popU256()
376
+ const byteVec = operandStack.popByteVec()
377
+ if (byteVec.kind === 'Symbol-ByteVec' || start.kind === 'Symbol-U256' || end.kind === 'Symbol-U256') {
378
+ operandStack.push({ kind: 'Symbol-ByteVec', value: undefined })
379
+ } else {
380
+ operandStack.push({
381
+ kind: 'ByteVec',
382
+ value: byteVec.value.slice(Number(start.value), Number(end.value))
383
+ })
384
+ }
385
+ break
386
+ }
387
+ case 'AddressEq': {
388
+ const address1 = operandStack.popAddress()
389
+ const address2 = operandStack.popAddress()
390
+ comparisonOp<'Address', LockupScript>(
391
+ address1,
392
+ address2,
393
+ (x, y) => arrayEquals(lockupScriptCodec.encode(x), lockupScriptCodec.encode(y)),
394
+ operandStack.push
395
+ )
396
+ break
397
+ }
398
+ case 'AddressNeq': {
399
+ const address1 = operandStack.popAddress()
400
+ const address2 = operandStack.popAddress()
401
+ comparisonOp<'Address', LockupScript>(
402
+ address1,
403
+ address2,
404
+ (x, y) => !arrayEquals(lockupScriptCodec.encode(x), lockupScriptCodec.encode(y)),
405
+ operandStack.push
406
+ )
407
+ break
408
+ }
409
+ case 'AddressToByteVec': {
410
+ const address = operandStack.popAddress()
411
+ if (address.kind === 'Symbol-Address') {
412
+ operandStack.push({ kind: 'Symbol-ByteVec', value: undefined })
413
+ } else {
414
+ operandStack.push({ kind: 'ByteVec', value: lockupScriptCodec.encode(address.value) })
415
+ }
416
+ break
417
+ }
418
+ case 'Assert': {
419
+ const bool = operandStack.popBool()
420
+ if (!bool) {
421
+ throw new Error('Assertion failed')
422
+ }
423
+ break
424
+ }
425
+ case 'Blake2b':
426
+ case 'Sha256':
427
+ case 'Sha3':
428
+ case 'Keccak256': {
429
+ dummyImplementation(instr.name)
430
+ operandStack.popByteVec()
431
+ operandStack.push({ kind: 'ByteVec', value: new Uint8Array(32) })
432
+ break
433
+ }
434
+ case 'ByteVecToAddress': {
435
+ const byteVec = operandStack.popByteVec()
436
+ if (byteVec.kind === 'Symbol-ByteVec') {
437
+ operandStack.push({ kind: 'Symbol-Address', value: undefined })
438
+ } else {
439
+ operandStack.push({ kind: 'Address', value: lockupScriptCodec.decode(byteVec.value) })
440
+ }
441
+ break
442
+ }
443
+ case 'Zeros': {
444
+ const size = operandStack.popU256()
445
+ if (size.kind === 'Symbol-U256') {
446
+ operandStack.push({ kind: 'Symbol-ByteVec', value: undefined })
447
+ } else {
448
+ if (size.value > 4096) {
449
+ throw new Error('Zeros size is too large')
450
+ }
451
+ operandStack.push({ kind: 'ByteVec', value: new Uint8Array(Number(size.value)) })
452
+ }
453
+ break
454
+ }
455
+ case 'U256To1Byte':
456
+ case 'U256To2Byte':
457
+ case 'U256To4Byte':
458
+ case 'U256To8Byte':
459
+ case 'U256To16Byte':
460
+ case 'U256To32Byte': {
461
+ dummyImplementation(instr.name)
462
+ operandStack.popU256()
463
+ operandStack.push({ kind: 'Symbol-ByteVec', value: undefined })
464
+ break
465
+ }
466
+ case 'U256From1Byte':
467
+ case 'U256From2Byte':
468
+ case 'U256From4Byte':
469
+ case 'U256From8Byte':
470
+ case 'U256From16Byte':
471
+ case 'U256From32Byte': {
472
+ dummyImplementation(instr.name)
473
+ operandStack.popByteVec()
474
+ operandStack.push({ kind: 'Symbol-U256', value: undefined })
475
+ break
476
+ }
477
+ case 'CallExternal':
478
+ case 'CallExternalBySelector': {
479
+ const contractId = operandStack.popByteVec()
480
+ const returnLength = operandStack.popU256() // method return length
481
+ operandStack.popU256() // method args length
482
+
483
+ if (contractId.kind !== 'Symbol-ByteVec') {
484
+ contractCalls.push({
485
+ contractAddress: addressFromContractId(binToHex(contractId.value)),
486
+ approvedAttoAlphAmount: approved.getApprovedAttoAlph(),
487
+ approvedTokens: approved.getApprovedTokens()
488
+ })
489
+ }
490
+ approved.reset()
491
+
492
+ if (returnLength.kind !== 'Symbol-U256') {
493
+ for (let i = 0; i < returnLength.value; i++) {
494
+ operandStack.push({ kind: 'Symbol-Any', value: undefined })
495
+ }
496
+ }
497
+ break
498
+ }
499
+ case 'ContractIdToAddress': {
500
+ const contractId = operandStack.popByteVec()
501
+ if (contractId.kind === 'Symbol-ByteVec') {
502
+ operandStack.push({ kind: 'Symbol-Address', value: undefined })
503
+ } else {
504
+ operandStack.push({ kind: 'Address', value: { kind: 'P2C', value: contractId.value } })
505
+ }
506
+ break
507
+ }
508
+ case 'LoadLocalByIndex': {
509
+ const index = operandStack.popU256()
510
+ if (index.kind === 'Symbol-U256') {
511
+ throw new Error('LoadLocalByIndex index is a symbol')
512
+ } else {
513
+ operandStack.push(localVariables.get(Number(index.value)))
514
+ }
515
+ break
516
+ }
517
+ case 'StoreLocalByIndex': {
518
+ const index = operandStack.popU256()
519
+ if (index.kind === 'Symbol-U256') {
520
+ throw new Error('StoreLocalByIndex index is a symbol')
521
+ } else {
522
+ localVariables.set(Number(index.value), operandStack.pop())
523
+ }
524
+ break
525
+ }
526
+ case 'CallerAddress': {
527
+ operandStack.push(callerAddress)
528
+ break
529
+ }
530
+ case 'ApproveAlph': {
531
+ const amount = operandStack.popU256() // amount
532
+ const spender = operandStack.popAddress() // spender
533
+ if (spender.kind.startsWith('Symbol')) {
534
+ approved.setUnknown() // The spender might be the caller
535
+ } else if (spender === callerAddress) {
536
+ approved.addApprovedAttoAlph(amount)
537
+ }
538
+ break
539
+ }
540
+ case 'ApproveToken': {
541
+ const amount = operandStack.popU256() // amount
542
+ const tokenId = operandStack.popByteVec() // token
543
+ const spender = operandStack.popAddress() // spender
544
+ if (spender.kind.startsWith('Symbol')) {
545
+ approved.setUnknown() // The spender might be the caller
546
+ } else if (spender === callerAddress) {
547
+ approved.addApprovedToken(tokenId, amount)
548
+ }
549
+ break
550
+ }
551
+ case 'CreateContractAndTransferToken': {
552
+ operandStack.popAddress() // token owner
553
+ }
554
+ case 'CreateContractWithToken': {
555
+ operandStack.popU256() // token amount
556
+ }
557
+ case 'CreateContract': {
558
+ operandStack.popByteVec() // mutable fields
559
+ operandStack.popByteVec() // immutable fields
560
+ operandStack.popByteVec() // contract code
561
+ operandStack.push({ kind: 'Symbol-ByteVec', value: undefined }) // new contract id
562
+ break
563
+ }
564
+ case 'TransferAlph': {
565
+ operandStack.popU256() // amount
566
+ operandStack.popAddress() // recipient
567
+ operandStack.popAddress() // sender
568
+ break
569
+ }
570
+ case 'TransferToken': {
571
+ operandStack.popU256() // amount
572
+ operandStack.popByteVec() // token
573
+ operandStack.popAddress() // recipient
574
+ operandStack.popAddress() // sender
575
+ break
576
+ }
577
+ default:
578
+ unimplemented(instr.name)
579
+ break
580
+ }
581
+ }
582
+ return contractCalls
583
+ }
584
+ }
585
+
586
+ type SymbolBool = { kind: 'Symbol-Bool'; value: undefined }
587
+ type SymbolI256 = { kind: 'Symbol-I256'; value: undefined }
588
+ type SymbolU256 = { kind: 'Symbol-U256'; value: undefined }
589
+ type SymbolByteVec = { kind: 'Symbol-ByteVec'; value: undefined }
590
+ type SymbolAddress = { kind: 'Symbol-Address'; value: undefined }
591
+ type SymbolAny = { kind: 'Symbol-Any'; value: undefined }
592
+ type SimulatorVal = Val | SymbolBool | SymbolI256 | SymbolU256 | SymbolByteVec | SymbolAddress | SymbolAny
593
+ type SimulatorVar<K extends string, V> = { kind: K; value: V } | { kind: `Symbol-${K}`; value: undefined }
594
+
595
+ function unaryOp<K extends string, V>(x: SimulatorVar<K, V>, op: (x: V) => V): SimulatorVar<K, V> {
596
+ if (x.kind.startsWith('Symbol')) {
597
+ return x
598
+ } else {
599
+ return { kind: x.kind as K, value: op(x.value as V) }
600
+ }
601
+ }
602
+
603
+ function binaryOp<K extends string, V>(
604
+ x: SimulatorVar<K, V>,
605
+ y: SimulatorVar<K, V>,
606
+ op: (x: V, y: V) => V,
607
+ push: (z: SimulatorVar<K, V>) => void
608
+ ): void {
609
+ const result = x.kind.startsWith('Symbol')
610
+ ? x
611
+ : y.kind.startsWith('Symbol')
612
+ ? y
613
+ : { kind: x.kind as K, value: op(x.value as V, y.value as V) }
614
+ push(result)
615
+ }
616
+
617
+ function comparisonOp<K extends string, V>(
618
+ x: SimulatorVar<K, V>,
619
+ y: SimulatorVar<K, V>,
620
+ op: (x: V, y: V) => boolean,
621
+ push: (z: SimulatorVar<'Bool', boolean>) => void
622
+ ): void {
623
+ const result: SimulatorVar<'Bool', boolean> =
624
+ x.kind.startsWith('Symbol') || y.kind.startsWith('Symbol')
625
+ ? { kind: 'Symbol-Bool', value: undefined }
626
+ : { kind: 'Bool', value: op(x.value as V, y.value as V) }
627
+ push(result)
628
+ }
629
+
630
+ // implement arrayEquals
631
+ function arrayEquals(x: Uint8Array, y: Uint8Array): boolean {
632
+ return x.length === y.length && x.every((value, index) => value === y[`${index}`])
633
+ }
634
+
635
+ // generate 32 bytes array with random numbers
636
+ function random32Bytes(): Uint8Array {
637
+ const result = new Uint8Array(32)
638
+ for (let i = 0; i < 32; i++) {
639
+ result[`${i}`] = Math.floor(Math.random() * 256)
640
+ }
641
+ return result
642
+ }
643
+
644
+ class Stack {
645
+ private stack: SimulatorVal[] = []
646
+
647
+ constructor() {
648
+ // TODO
649
+ }
650
+
651
+ push = (val: SimulatorVal) => {
652
+ this.stack.push(val)
653
+ }
654
+
655
+ pop(): SimulatorVal {
656
+ const result = this.stack.pop()
657
+ if (result === undefined) {
658
+ throw new Error('Stack is empty')
659
+ }
660
+ return result
661
+ }
662
+
663
+ size(): number {
664
+ return this.stack.length
665
+ }
666
+
667
+ checkedResult(result: SimulatorVal, expected: string): SimulatorVal {
668
+ if (result.kind.startsWith('Symbol')) {
669
+ if (result.kind !== `Symbol-${expected}`) {
670
+ throw new Error(`Expected a ${expected} value on the stack`)
671
+ }
672
+ return result
673
+ }
674
+ if (result.kind !== expected) {
675
+ throw new Error(`Expected a ${expected} value on the stack`)
676
+ }
677
+ return result
678
+ }
679
+
680
+ popBool(): ValBool | SymbolBool {
681
+ const result = this.pop()
682
+ return this.checkedResult(result, 'Bool') as ValBool | SymbolBool
683
+ }
684
+
685
+ popI256(): ValI256 | SymbolI256 {
686
+ const result = this.pop()
687
+ return this.checkedResult(result, 'I256') as ValI256 | SymbolI256
688
+ }
689
+
690
+ popU256(): ValU256 | SymbolU256 {
691
+ const result = this.pop()
692
+ return this.checkedResult(result, 'U256') as ValU256 | SymbolU256
693
+ }
694
+
695
+ popByteVec(): ValByteVec | SymbolByteVec {
696
+ const result = this.pop()
697
+ return this.checkedResult(result, 'ByteVec') as ValByteVec | SymbolByteVec
698
+ }
699
+
700
+ popAddress(): ValAddress | SymbolAddress {
701
+ const result = this.pop()
702
+ return this.checkedResult(result, 'Address') as ValAddress | SymbolAddress
703
+ }
704
+ }
705
+
706
+ class LocalVariables {
707
+ private locals: SimulatorVal | undefined[] = []
708
+
709
+ constructor() {
710
+ // TODO
711
+ }
712
+
713
+ get(index: number): SimulatorVal {
714
+ const result = this.locals[`${index}`]
715
+ if (result === undefined) {
716
+ throw new Error(`Local variable at index ${index} is not set`)
717
+ }
718
+ return result
719
+ }
720
+
721
+ set(index: number, val: SimulatorVal): void {
722
+ this.locals[`${index}`] = val
723
+ }
724
+
725
+ private checkedResult(result: SimulatorVal, index: number, expected: string): SimulatorVal {
726
+ if (result.kind.startsWith('Symbol')) {
727
+ if (result.kind !== `Symbol-${expected}`) {
728
+ throw new Error(`Local variable at index ${index} is not a ${expected}`)
729
+ }
730
+ return result
731
+ }
732
+ if (result.kind !== expected) {
733
+ throw new Error(`Local variable at index ${index} is not a ${expected}`)
734
+ }
735
+ return result
736
+ }
737
+
738
+ getBool(index: number): ValBool | SymbolBool {
739
+ const result = this.get(index)
740
+ return this.checkedResult(result, index, 'Bool') as ValBool | SymbolBool
741
+ }
742
+
743
+ getI256(index: number): ValI256 | SymbolI256 {
744
+ const result = this.get(index)
745
+ return this.checkedResult(result, index, 'I256') as ValI256 | SymbolI256
746
+ }
747
+
748
+ getU256(index: number): ValU256 | SymbolU256 {
749
+ const result = this.get(index)
750
+ return this.checkedResult(result, index, 'U256') as ValU256 | SymbolU256
751
+ }
752
+
753
+ getByteVec(index: number): ValByteVec | SymbolByteVec {
754
+ const result = this.get(index)
755
+ return this.checkedResult(result, index, 'ByteVec') as ValByteVec | SymbolByteVec
756
+ }
757
+
758
+ getAddress(index: number): ValAddress | SymbolAddress {
759
+ const result = this.get(index)
760
+ return this.checkedResult(result, index, 'Address') as ValAddress | SymbolAddress
761
+ }
762
+ }
763
+
764
+ function unimplemented(instrName: string): never {
765
+ throw new Error(`Unimplemented instruction: ${instrName}`)
766
+ }
767
+
768
+ function dummyImplementation(instrName: string): void {
769
+ console.debug(`Dummy implementation for instruction: ${instrName}`)
770
+ }
771
+
772
+ class ApprovedAccumulator {
773
+ private approvedTokens: { id: string; amount: bigint | 'unknown' }[] | 'unknown' = []
774
+
775
+ constructor() {
776
+ this.reset()
777
+ }
778
+
779
+ reset(): void {
780
+ this.approvedTokens = [{ id: ALPH_TOKEN_ID, amount: 0n }]
781
+ }
782
+
783
+ setUnknown(): void {
784
+ this.approvedTokens = 'unknown'
785
+ }
786
+
787
+ getApprovedAttoAlph(): bigint | 'unknown' | undefined {
788
+ if (this.approvedTokens === 'unknown') {
789
+ return 'unknown'
790
+ }
791
+
792
+ const approvedAttoAlph = this.approvedTokens[0].amount
793
+ return approvedAttoAlph === 'unknown' ? 'unknown' : approvedAttoAlph === 0n ? undefined : approvedAttoAlph
794
+ }
795
+
796
+ getApprovedTokens(): { id: string; amount: bigint | 'unknown' }[] | 'unknown' | undefined {
797
+ if (this.approvedTokens === 'unknown') {
798
+ return 'unknown'
799
+ }
800
+
801
+ const allTokens = this.approvedTokens.slice(1)
802
+ return allTokens.length === 0 ? undefined : allTokens
803
+ }
804
+
805
+ addApprovedAttoAlph(amount: ValU256 | SymbolU256): void {
806
+ this.addApprovedToken({ kind: 'ByteVec', value: hexToBinUnsafe(ALPH_TOKEN_ID) }, amount)
807
+ }
808
+
809
+ addApprovedToken(tokenId: ValByteVec | SymbolByteVec, amount: ValU256 | SymbolU256): void {
810
+ if (this.approvedTokens === 'unknown') {
811
+ return
812
+ }
813
+
814
+ if (tokenId.kind === 'Symbol-ByteVec') {
815
+ this.approvedTokens = 'unknown'
816
+ return
817
+ }
818
+
819
+ const tokenIndex = this.approvedTokens.findIndex((token) => arrayEquals(hexToBinUnsafe(token.id), tokenId.value))
820
+ if (tokenIndex === -1) {
821
+ this.approvedTokens.push({
822
+ id: binToHex(tokenId.value),
823
+ amount: amount.kind === 'Symbol-U256' ? 'unknown' : amount.value
824
+ })
825
+ } else {
826
+ const approved = this.approvedTokens[`${tokenIndex}`]
827
+ if (approved.amount === 'unknown') {
828
+ return
829
+ }
830
+
831
+ if (amount.kind === 'Symbol-U256') {
832
+ approved.amount = 'unknown'
833
+ } else {
834
+ approved.amount += amount.value
835
+ }
836
+ }
837
+ }
838
+ }