@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.
- package/dist/alephium-web3.min.js +1 -1
- package/dist/alephium-web3.min.js.map +1 -1
- package/dist/src/api/api-alephium.d.ts +5 -0
- package/dist/src/api/types.d.ts +1 -1
- package/dist/src/codec/array-codec.d.ts +17 -0
- package/dist/src/codec/array-codec.js +59 -0
- package/dist/src/codec/asset-output-codec.d.ts +27 -0
- package/dist/src/codec/asset-output-codec.js +135 -0
- package/dist/src/codec/bigint-codec.d.ts +5 -0
- package/dist/src/codec/bigint-codec.js +86 -0
- package/dist/src/codec/bytestring-codec.d.ts +16 -0
- package/dist/src/codec/bytestring-codec.js +50 -0
- package/dist/src/codec/codec.d.ts +8 -0
- package/dist/src/codec/codec.js +9 -0
- package/dist/src/codec/compact-int-codec.d.ts +51 -0
- package/dist/src/codec/compact-int-codec.js +300 -0
- package/dist/src/codec/contract-codec.d.ts +23 -0
- package/dist/src/codec/contract-codec.js +81 -0
- package/dist/src/codec/contract-output-codec.d.ts +21 -0
- package/dist/src/codec/contract-output-codec.js +82 -0
- package/dist/src/codec/contract-output-ref-codec.d.ts +15 -0
- package/dist/src/codec/contract-output-ref-codec.js +38 -0
- package/dist/src/codec/either-codec.d.ts +17 -0
- package/dist/src/codec/either-codec.js +67 -0
- package/dist/src/codec/hash.d.ts +4 -0
- package/dist/src/codec/hash.js +23 -0
- package/dist/src/codec/index.d.ts +23 -0
- package/dist/src/codec/index.js +69 -0
- package/dist/src/codec/input-codec.d.ts +22 -0
- package/dist/src/codec/input-codec.js +71 -0
- package/dist/src/codec/instr-codec.d.ts +230 -0
- package/dist/src/codec/instr-codec.js +471 -0
- package/dist/src/codec/lockup-script-codec.d.ts +28 -0
- package/dist/src/codec/lockup-script-codec.js +80 -0
- package/dist/src/codec/long-codec.d.ts +9 -0
- package/dist/src/codec/long-codec.js +56 -0
- package/dist/src/codec/method-codec.d.ts +31 -0
- package/dist/src/codec/method-codec.js +78 -0
- package/dist/src/codec/option-codec.d.ts +15 -0
- package/dist/src/codec/option-codec.js +55 -0
- package/dist/src/codec/output-codec.d.ts +7 -0
- package/dist/src/codec/output-codec.js +26 -0
- package/dist/src/codec/script-codec.d.ts +16 -0
- package/dist/src/codec/script-codec.js +41 -0
- package/dist/src/codec/signature-codec.d.ts +14 -0
- package/dist/src/codec/signature-codec.js +19 -0
- package/dist/src/codec/signed-int-codec.d.ts +9 -0
- package/dist/src/codec/signed-int-codec.js +39 -0
- package/dist/src/codec/token-codec.d.ts +16 -0
- package/dist/src/codec/token-codec.js +46 -0
- package/dist/src/codec/transaction-codec.d.ts +27 -0
- package/dist/src/codec/transaction-codec.js +128 -0
- package/dist/src/codec/unlock-script-codec.d.ts +40 -0
- package/dist/src/codec/unlock-script-codec.js +170 -0
- package/dist/src/codec/unsigned-tx-codec.d.ts +30 -0
- package/dist/src/codec/unsigned-tx-codec.js +103 -0
- package/dist/src/contract/contract.d.ts +10 -4
- package/dist/src/contract/contract.js +184 -11
- package/dist/src/contract/ralph.d.ts +16 -0
- package/dist/src/contract/ralph.js +127 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +2 -1
- package/dist/src/utils/address.d.ts +2 -0
- package/dist/src/utils/address.js +18 -6
- package/package.json +4 -3
- package/src/api/api-alephium.ts +6 -0
- package/src/api/types.ts +1 -1
- package/src/codec/array-codec.ts +63 -0
- package/src/codec/asset-output-codec.ts +149 -0
- package/src/codec/bigint-codec.ts +92 -0
- package/src/codec/bytestring-codec.ts +56 -0
- package/src/codec/codec.ts +31 -0
- package/src/codec/compact-int-codec.ts +316 -0
- package/src/codec/contract-codec.ts +95 -0
- package/src/codec/contract-output-codec.ts +95 -0
- package/src/codec/contract-output-ref-codec.ts +42 -0
- package/src/codec/either-codec.ts +74 -0
- package/src/codec/hash.ts +35 -0
- package/src/codec/index.ts +41 -0
- package/src/codec/input-codec.ts +81 -0
- package/src/codec/instr-codec.ts +479 -0
- package/src/codec/lockup-script-codec.ts +99 -0
- package/src/codec/long-codec.ts +59 -0
- package/src/codec/method-codec.ts +97 -0
- package/src/codec/option-codec.ts +60 -0
- package/src/codec/output-codec.ts +26 -0
- package/src/codec/script-codec.ts +45 -0
- package/src/codec/signature-codec.ts +40 -0
- package/src/codec/signed-int-codec.ts +37 -0
- package/src/codec/token-codec.ts +51 -0
- package/src/codec/transaction-codec.ts +147 -0
- package/src/codec/unlock-script-codec.ts +194 -0
- package/src/codec/unsigned-tx-codec.ts +124 -0
- package/src/contract/contract.ts +271 -14
- package/src/contract/ralph.ts +140 -2
- package/src/index.ts +1 -1
- 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()
|
package/src/contract/contract.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
1828
|
-
|
|
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
|
|
1834
|
-
const
|
|
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
|
|
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
|
|
1842
|
-
contract.
|
|
1843
|
-
|
|
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 {
|