@helios-lang/effect 0.1.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 (64) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +3 -0
  3. package/dist/Address.js +13 -0
  4. package/dist/Address.js.map +1 -0
  5. package/dist/Bech32.js +153 -0
  6. package/dist/Bech32.js.map +1 -0
  7. package/dist/Cbor.js +1171 -0
  8. package/dist/Cbor.js.map +1 -0
  9. package/dist/Uplc/Cek.js +3 -0
  10. package/dist/Uplc/Cek.js.map +1 -0
  11. package/dist/Uplc/Data.js +171 -0
  12. package/dist/Uplc/Data.js.map +1 -0
  13. package/dist/Uplc/DataSchema.js +118 -0
  14. package/dist/Uplc/DataSchema.js.map +1 -0
  15. package/dist/Uplc/Primitive.js +23 -0
  16. package/dist/Uplc/Primitive.js.map +1 -0
  17. package/dist/Uplc/index.js +4 -0
  18. package/dist/Uplc/index.js.map +1 -0
  19. package/dist/index.js +5 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/internal/Base32.js +201 -0
  22. package/dist/internal/Base32.js.map +1 -0
  23. package/dist/internal/BigEndian.js +56 -0
  24. package/dist/internal/BigEndian.js.map +1 -0
  25. package/dist/internal/Bits.js +300 -0
  26. package/dist/internal/Bits.js.map +1 -0
  27. package/dist/internal/Bytes.js +293 -0
  28. package/dist/internal/Bytes.js.map +1 -0
  29. package/dist/internal/Flat.js +298 -0
  30. package/dist/internal/Flat.js.map +1 -0
  31. package/dist/internal/Float.js +154 -0
  32. package/dist/internal/Float.js.map +1 -0
  33. package/dist/internal/Utf8.js +44 -0
  34. package/dist/internal/Utf8.js.map +1 -0
  35. package/eslint.config.mjs +30 -0
  36. package/package.json +36 -0
  37. package/src/Address.ts +20 -0
  38. package/src/Bech32.test.ts +117 -0
  39. package/src/Bech32.ts +198 -0
  40. package/src/Cbor.test.ts +1610 -0
  41. package/src/Cbor.ts +1704 -0
  42. package/src/Uplc/Cek.ts +92 -0
  43. package/src/Uplc/Data.ts +259 -0
  44. package/src/Uplc/DataSchema.test.ts +207 -0
  45. package/src/Uplc/DataSchema.ts +181 -0
  46. package/src/Uplc/Primitive.ts +56 -0
  47. package/src/Uplc/index.ts +3 -0
  48. package/src/index.ts +4 -0
  49. package/src/internal/Base32.test.ts +219 -0
  50. package/src/internal/Base32.ts +341 -0
  51. package/src/internal/BigEndian.test.ts +79 -0
  52. package/src/internal/BigEndian.ts +67 -0
  53. package/src/internal/Bits.test.ts +300 -0
  54. package/src/internal/Bits.ts +398 -0
  55. package/src/internal/Bytes.test.ts +369 -0
  56. package/src/internal/Bytes.ts +343 -0
  57. package/src/internal/Flat.test.ts +29 -0
  58. package/src/internal/Flat.ts +387 -0
  59. package/src/internal/Float.test.ts +51 -0
  60. package/src/internal/Float.ts +190 -0
  61. package/src/internal/Utf8.test.ts +69 -0
  62. package/src/internal/Utf8.ts +58 -0
  63. package/tsconfig.build.json +14 -0
  64. package/tsconfig.json +38 -0
@@ -0,0 +1,92 @@
1
+ //import { Either } from "effect"
2
+
3
+ /**
4
+ * The context that terms and frames need to operate.
5
+ */
6
+ export interface Context {
7
+ readonly cost: CostTracker
8
+ //getBuiltin(id: number): Builtin | undefined
9
+ print(message: string, site?: Site): void
10
+ popLastMessage(): string | undefined
11
+ }
12
+
13
+ export interface Cost {
14
+ readonly cpu: bigint
15
+ readonly mem: bigint
16
+ }
17
+
18
+ export type CostBreakdown = {
19
+ [name: string]: Cost & { count: number }
20
+ }
21
+
22
+ export interface CostModel {
23
+ readonly applyTerm: Cost
24
+ readonly builtinTerm: Cost
25
+ readonly caseTerm: Cost
26
+ readonly constTerm: Cost
27
+ readonly constrTerm: Cost
28
+ readonly delayTerm: Cost
29
+ readonly forceTerm: Cost
30
+ readonly lambdaTerm: Cost
31
+ readonly startupCost: Cost
32
+ readonly varTerm: Cost
33
+ readonly builtins: Record<string, (argSizes: number[]) => Cost>
34
+ }
35
+
36
+ export interface CostTracker {
37
+ readonly cost: Cost
38
+ readonly costModel: CostModel
39
+ readonly breakdown: CostBreakdown
40
+ incrApplyCost(): void
41
+ incrBuiltinCost(): void
42
+ incrCaseCost(): void
43
+ incrConstCost(): void
44
+ incrConstrCost(): void
45
+ incrDelayCost(): void
46
+ incrForceCost(): void
47
+ incrLambdaCost(): void
48
+ incrStartupCost(): void
49
+ incrVarCost(): void
50
+ incrArgSizesCost(name: string, argSizes: bigint[]): void
51
+ }
52
+
53
+ /**
54
+ * Instantiate a `Machine` with {@link makeCekMachine}.
55
+ */
56
+ //export interface Machine extends Context {
57
+ // readonly builtins: Builtin[]
58
+ // readonly logger: Logger | undefined
59
+ // readonly state: State
60
+ // readonly trace: { message: string; site?: Site }[]
61
+ // eval(): Result
62
+ //}
63
+
64
+ /**
65
+ * TODO: rename to CEKResult
66
+ * @typedef {{
67
+ * result: Either<
68
+ * {
69
+ * error: string
70
+ * callSites: CallSiteInfo[]
71
+ * },
72
+ * string | UplcValue
73
+ * >
74
+ * cost: Cost
75
+ * logs: {message: string, site?: Site}[]
76
+ * breakdown: CostBreakdown
77
+ * }} CekResult
78
+ * Return value is optional and can be omitted if the UplcValue doesn't suffice to contain it (eg. lambda functions).
79
+ */
80
+ //export interface Result {
81
+ // result: Either.Either<
82
+ // string | Value,
83
+ // { error: string; callSites: CallSiteInfo[] }
84
+ // >
85
+ //}
86
+
87
+ export interface Site {
88
+ readonly file: string
89
+ readonly line: number
90
+ readonly column: number
91
+ readonly description: string | undefined
92
+ }
@@ -0,0 +1,259 @@
1
+ import { Effect, Schema } from "effect"
2
+ import * as Bytes from "../internal/Bytes.js"
3
+ import * as Cbor from "../Cbor.js"
4
+
5
+ const SuspendedData = Schema.suspend(
6
+ (): Schema.Schema<Data, DataEncoded> => Data
7
+ )
8
+
9
+ export const ByteArray = Schema.Struct({
10
+ bytes: Schema.String
11
+ })
12
+
13
+ export type ByteArray = Schema.Schema.Type<typeof ByteArray>
14
+
15
+ export type ByteArrayEncoded = Schema.Schema.Encoded<typeof ByteArray>
16
+
17
+ export function makeByteArray(
18
+ bytes: string | number[] | Uint8Array
19
+ ): ByteArray {
20
+ return { bytes: Bytes.toHex(bytes) }
21
+ }
22
+
23
+ /**
24
+ * No need to serialize
25
+ *
26
+ * Cannot used Branded types due Schema issues.
27
+ */
28
+ export const Int = Schema.Struct({
29
+ int: Schema.BigIntFromNumber
30
+ })
31
+
32
+ export type Int = Schema.Schema.Type<typeof Int>
33
+
34
+ export type IntEncoded = Schema.Schema.Encoded<typeof Int>
35
+
36
+ export function makeInt(value: number | bigint): Int {
37
+ return { int: BigInt(value) }
38
+ }
39
+
40
+ export const List = Schema.Struct({
41
+ list: Schema.Array(SuspendedData)
42
+ })
43
+
44
+ /**
45
+ * Must be defined explicitly to avoid circular reference problems
46
+ */
47
+ export type List = {
48
+ readonly list: ReadonlyArray<Data>
49
+ }
50
+
51
+ export type ListEncoded = {
52
+ readonly list: ReadonlyArray<DataEncoded>
53
+ }
54
+
55
+ export function makeList(items: Data[]): List {
56
+ return {
57
+ list: items
58
+ }
59
+ }
60
+
61
+ export const Map = Schema.Struct({
62
+ map: Schema.Array(
63
+ Schema.Struct({
64
+ k: SuspendedData,
65
+ v: SuspendedData
66
+ })
67
+ )
68
+ })
69
+
70
+ /**
71
+ * Must be defined explicitly to avoid circular reference problems
72
+ */
73
+ export type Map = {
74
+ readonly map: ReadonlyArray<{
75
+ readonly k: Data
76
+ readonly v: Data
77
+ }>
78
+ }
79
+
80
+ export type MapEncoded = {
81
+ readonly map: ReadonlyArray<{
82
+ readonly k: DataEncoded
83
+ readonly v: DataEncoded
84
+ }>
85
+ }
86
+
87
+ export function makeMap(entries: [Data, Data][]): Map {
88
+ return {
89
+ map: entries.map(([k, v]) => ({ k, v }))
90
+ }
91
+ }
92
+
93
+ export const Constr = Schema.Struct({
94
+ constructor: Schema.Number,
95
+ fields: Schema.Array(SuspendedData)
96
+ })
97
+
98
+ /**
99
+ * Must be defined explicitly to avoid circular reference problems
100
+ */
101
+ export type Constr = {
102
+ readonly constructor: number
103
+ readonly fields: ReadonlyArray<Data>
104
+ }
105
+
106
+ export type ConstrEncoded = {
107
+ readonly constructor: number
108
+ readonly fields: ReadonlyArray<DataEncoded>
109
+ }
110
+
111
+ export function makeConstr(tag: bigint | number, fields: Data[]): Constr {
112
+ return {
113
+ constructor: Number(tag),
114
+ fields
115
+ }
116
+ }
117
+
118
+ export const Data = Schema.Union(ByteArray, Constr, Int, List, Map, Constr)
119
+
120
+ export const DataUnencoded = Schema.typeSchema(Data)
121
+
122
+ export type DataUnencoded = Data
123
+
124
+ /**
125
+ * Must be defined explicitly to avoid circular reference problems
126
+ */
127
+ export type Data = ByteArray | Constr | Int | List | Map
128
+
129
+ export type DataEncoded =
130
+ | ByteArrayEncoded
131
+ | ConstrEncoded
132
+ | IntEncoded
133
+ | ListEncoded
134
+ | MapEncoded
135
+
136
+ /**
137
+ * Simple recursive CBOR decoder
138
+ * @param bytes
139
+ * @returns
140
+ */
141
+ export const decode = (bytes: Bytes.BytesLike): Cbor.DecodeEffect<Data> =>
142
+ Effect.gen(function* () {
143
+ const stream = Bytes.makeStream(bytes)
144
+
145
+ if (yield* Cbor.isList(stream)) {
146
+ const items = yield* Cbor.decodeList(decode)(stream)
147
+
148
+ return makeList(items)
149
+ } else if (yield* Cbor.isBytes(stream)) {
150
+ return makeByteArray(yield* Cbor.decodeBytes(stream))
151
+ } else if (yield* Cbor.isMap(stream)) {
152
+ const entries = yield* Cbor.decodeMap(decode, decode)(stream)
153
+
154
+ return makeMap(entries)
155
+ } else if (yield* Cbor.isConstr(stream)) {
156
+ const [tag, fields] = yield* Cbor.decodeConstr(decode)(stream)
157
+ return makeConstr(tag, fields)
158
+ } else {
159
+ return makeInt(yield* Cbor.decodeInt(stream))
160
+ }
161
+ })
162
+
163
+ /**
164
+ * Simple recursive CBOR encoder
165
+ * @param data
166
+ * @returns
167
+ */
168
+ export function encode(data: Data): number[] {
169
+ if ("bytes" in data) {
170
+ return Cbor.encodeBytes(data.bytes.slice(), true)
171
+ } else if ("fields" in data) {
172
+ return Cbor.encodeConstr(data.constructor, data.fields.map(encode))
173
+ } else if ("int" in data) {
174
+ return Cbor.encodeInt(data.int)
175
+ } else if ("list" in data) {
176
+ return Cbor.encodeList(data.list.map(encode))
177
+ } else if ("map" in data) {
178
+ return Cbor.encodeMap(data.map.map(({ k, v }) => [encode(k), encode(v)]))
179
+ } else {
180
+ throw new Error("Unrecognized Uplc.Data type")
181
+ }
182
+ }
183
+
184
+ export const NODE_MEM_SIZE = 4
185
+
186
+ /**
187
+ * Simple recursive algorithm
188
+ * @param data
189
+ * @returns
190
+ */
191
+ export function memSize(data: Data): number {
192
+ if ("bytes" in data) {
193
+ return NODE_MEM_SIZE + calcBytesMemSize(data.bytes)
194
+ } else if ("fields" in data) {
195
+ return data.fields.reduce(
196
+ (prev, field) => prev + memSize(field),
197
+ NODE_MEM_SIZE
198
+ )
199
+ } else if ("int" in data) {
200
+ return NODE_MEM_SIZE + calcIntMemSize(data.int)
201
+ } else if ("list" in data) {
202
+ return data.list.reduce((prev, item) => prev + memSize(item), NODE_MEM_SIZE)
203
+ } else if ("map" in data) {
204
+ return data.map.reduce(
205
+ (prev, { k, v }) => prev + memSize(k) + memSize(v),
206
+ NODE_MEM_SIZE
207
+ )
208
+ } else {
209
+ throw new Error("Unrecognized Uplc.Data type")
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Calculates the mem size of a byte array without the DATA_NODE overhead.
215
+ * @param bytes
216
+ * @returns
217
+ */
218
+ export function calcBytesMemSize(
219
+ bytes: string | readonly number[] | Uint8Array
220
+ ): number {
221
+ const n = Bytes.toArray(bytes).length
222
+
223
+ if (n === 0) {
224
+ return 1 // this is so annoying: haskell reference implementation says it should be 0, but current (20220925) testnet and mainnet settings say it's 1
225
+ } else {
226
+ return Math.floor((n - 1) / 8) + 1
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Calculate the mem size of a integer (without the DATA_NODE overhead)
232
+ * @param value
233
+ * @returns
234
+ */
235
+ export function calcIntMemSize(value: bigint) {
236
+ if (value == 0n) {
237
+ return 1
238
+ } else {
239
+ const abs = value > 0n ? value : -value
240
+
241
+ return Math.floor(log2i(abs) / 64) + 1
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Math.log2 truncates, but we need a rounding down version
247
+ * @param x positive number
248
+ * @returns
249
+ */
250
+ export function log2i(x: bigint): number {
251
+ let p = 0
252
+
253
+ while (x > 1n) {
254
+ x >>= 1n
255
+ p++
256
+ }
257
+
258
+ return p
259
+ }
@@ -0,0 +1,207 @@
1
+ import { describe, expect, it } from "bun:test"
2
+ import { Schema } from "effect"
3
+ import * as DataSchema from "./DataSchema.js"
4
+
5
+ describe("Uplc.DataSchema.BigInt", () => {
6
+ it("succeeds for {int: 0n}", () => {
7
+ expect(Schema.decodeSync(DataSchema.BigInt)({ int: 0n })).toBe(0n)
8
+ })
9
+
10
+ it("fails for {int: '0'}", () => {
11
+ expect(() =>
12
+ Schema.decodeUnknownSync(DataSchema.BigInt)({ int: "0" })
13
+ ).toThrow()
14
+ })
15
+ })
16
+
17
+ describe("Uplc.DataSchema.Int", () => {
18
+ it("succeeds for {int: 0n}", () => {
19
+ expect(Schema.decodeSync(DataSchema.Int)({ int: 0n })).toBe(0)
20
+ })
21
+
22
+ it("fails for {int: '0'}", () => {
23
+ expect(() =>
24
+ Schema.decodeUnknownSync(DataSchema.Int)({ int: "0" })
25
+ ).toThrow()
26
+ })
27
+ })
28
+
29
+ describe("Uplc.DataSchema.String", () => {
30
+ it("succeeds for {bytes: ''}", () => {
31
+ expect(Schema.decodeSync(DataSchema.String)({ bytes: "" })).toBe("")
32
+ })
33
+
34
+ it("fails for {bytes: 'ff'}", () => {
35
+ expect(() =>
36
+ Schema.decodeSync(DataSchema.String)({ bytes: "ff" })
37
+ ).toThrow()
38
+ })
39
+
40
+ it("succeeds for {bytes: '48656C6C6F20576F726C64'}", () => {
41
+ expect(
42
+ Schema.decodeSync(DataSchema.String)({ bytes: "48656C6C6F20576F726C64" })
43
+ ).toBe("Hello World")
44
+ })
45
+ })
46
+
47
+ describe("Uplc.DataSchema.Array", () => {
48
+ it("succeeds for empty ListData", () => {
49
+ expect(
50
+ Schema.decodeSync(DataSchema.Array(DataSchema.String))({ list: [] })
51
+ ).toEqual([])
52
+ })
53
+
54
+ it("succeeds for ListData containg single 'Hello World' string", () => {
55
+ expect(
56
+ Schema.decodeSync(DataSchema.Array(DataSchema.String))({
57
+ list: [{ bytes: "48656C6C6F20576F726C64" }]
58
+ })
59
+ ).toEqual(["Hello World"])
60
+ })
61
+
62
+ it("fails if ListData items are heterogenous", () => {
63
+ expect(() =>
64
+ Schema.decodeSync(DataSchema.Array(DataSchema.String))({
65
+ list: [{ bytes: "48656C6C6F20576F726C64" }, { int: 0n }]
66
+ })
67
+ ).toThrow()
68
+ })
69
+ })
70
+
71
+ describe("Uplc.DataSchema.Struct", () => {
72
+ it("succeeds for empty ListData for empty Struct", () => {
73
+ expect(Schema.decodeSync(DataSchema.Struct({}))({ list: [] })).toEqual({})
74
+ })
75
+
76
+ it("fails for empty ListData if one field is defined", () => {
77
+ expect(() =>
78
+ Schema.decodeSync(DataSchema.Struct({ foo: DataSchema.String }))({
79
+ list: []
80
+ })
81
+ ).toThrow()
82
+ })
83
+
84
+ it("succeeds for ListData with single entry if one field is defined", () => {
85
+ expect(
86
+ Schema.decodeSync(DataSchema.Struct({ foo: DataSchema.String }))({
87
+ list: [{ bytes: "48656C6C6F20576F726C64" }]
88
+ })
89
+ ).toEqual({ foo: "Hello World" })
90
+ })
91
+
92
+ it("fails for ListData with wrong entry in first place with one field is defined", () => {
93
+ expect(() =>
94
+ Schema.decodeSync(DataSchema.Struct({ foo: DataSchema.String }))({
95
+ list: [{ int: 0n }]
96
+ })
97
+ ).toThrow()
98
+ })
99
+
100
+ it("succeeds for ListData with spurious entries at end with one field is defined", () => {
101
+ expect(
102
+ Schema.decodeSync(DataSchema.Struct({ foo: DataSchema.String }))({
103
+ list: [{ bytes: "48656C6C6F20576F726C64" }, { int: 0n }]
104
+ })
105
+ ).toEqual({ foo: "Hello World" })
106
+ })
107
+
108
+ it("succeeds for ListData with two entries with two fields", () => {
109
+ expect(
110
+ Schema.decodeSync(
111
+ DataSchema.Struct({ foo: DataSchema.String, bar: DataSchema.Int })
112
+ )({ list: [{ bytes: "48656C6C6F20576F726C64" }, { int: 0n }] })
113
+ ).toEqual({ foo: "Hello World", bar: 0 })
114
+ })
115
+
116
+ it("fails for ListData with two entries in wrong order with two fields", () => {
117
+ expect(() =>
118
+ Schema.decodeSync(
119
+ DataSchema.Struct({ bar: DataSchema.Int, foo: DataSchema.String })
120
+ )({ list: [{ bytes: "48656C6C6F20576F726C64" }, { int: 0n }] })
121
+ ).toThrow()
122
+ })
123
+ })
124
+
125
+ describe("Uplc.DataSchema.EnumVariant", () => {
126
+ it("succeeds for empty ConstrData for empty EnumVariant", () => {
127
+ expect(
128
+ Schema.decodeSync(DataSchema.EnumVariant(0, {}))({
129
+ constructor: 0,
130
+ fields: []
131
+ })
132
+ ).toEqual({})
133
+ })
134
+
135
+ it("fields for ConstrData with wrong tag", () => {
136
+ expect(() =>
137
+ Schema.decodeSync(DataSchema.EnumVariant(0, {}))({
138
+ constructor: 1,
139
+ fields: []
140
+ })
141
+ ).toThrow()
142
+ })
143
+
144
+ it("fails for empty ConstrData if one field is defined", () => {
145
+ expect(() =>
146
+ Schema.decodeSync(DataSchema.EnumVariant(0, { foo: DataSchema.String }))({
147
+ constructor: 0,
148
+ fields: []
149
+ })
150
+ ).toThrow()
151
+ })
152
+
153
+ it("succeeds for ConstrData with single entry if one field is defined", () => {
154
+ expect(
155
+ Schema.decodeSync(DataSchema.EnumVariant(0, { foo: DataSchema.String }))({
156
+ constructor: 0,
157
+ fields: [{ bytes: "48656C6C6F20576F726C64" }]
158
+ })
159
+ ).toEqual({ foo: "Hello World" })
160
+ })
161
+
162
+ it("fails for ConstrData with wrong entry in first place with one field is defined", () => {
163
+ expect(() =>
164
+ Schema.decodeSync(DataSchema.EnumVariant(0, { foo: DataSchema.String }))({
165
+ constructor: 0,
166
+ fields: [{ int: 0n }]
167
+ })
168
+ ).toThrow()
169
+ })
170
+
171
+ it("succeeds for EnumVariant with spurious entries at end with one field is defined", () => {
172
+ expect(
173
+ Schema.decodeSync(DataSchema.EnumVariant(0, { foo: DataSchema.String }))({
174
+ constructor: 0,
175
+ fields: [{ bytes: "48656C6C6F20576F726C64" }, { int: 0n }]
176
+ })
177
+ ).toEqual({ foo: "Hello World" })
178
+ })
179
+
180
+ it("succeeds for EnumVariant with two entries with two fields", () => {
181
+ expect(
182
+ Schema.decodeSync(
183
+ DataSchema.EnumVariant(0, {
184
+ foo: DataSchema.String,
185
+ bar: DataSchema.Int
186
+ })
187
+ )({
188
+ constructor: 0,
189
+ fields: [{ bytes: "48656C6C6F20576F726C64" }, { int: 0n }]
190
+ })
191
+ ).toEqual({ foo: "Hello World", bar: 0 })
192
+ })
193
+
194
+ it("fails for EnumVariant with two entries in wrong order with two fields", () => {
195
+ expect(() =>
196
+ Schema.decodeSync(
197
+ DataSchema.EnumVariant(0, {
198
+ bar: DataSchema.Int,
199
+ foo: DataSchema.String
200
+ })
201
+ )({
202
+ constructor: 0,
203
+ fields: [{ bytes: "48656C6C6F20576F726C64" }, { int: 0n }]
204
+ })
205
+ ).toThrow()
206
+ })
207
+ })